Initial commit: Messy-Little-Gadgets v1.0.0

This commit is contained in:
dvs
2026-08-28 10:51:16 +08:00
commit 490381c5c6
5 changed files with 333 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
# Python
__pycache__/
*.py[cod]
*.egg-info/
dist/
build/
.venv/
venv/
env/
*.mp3
*.wav
# System files
Thumbs.db
.DS_Store
desktop.ini
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 DVS (dvs-dvsxt)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+54
View File
@@ -0,0 +1,54 @@
# 🧰 Messy-Little-Gadgets
> A collection of small, handy Python utilities — random little tools for everyday tasks.
**Messy-Little-Gadgets** is a growing collection of miscellaneous Python scripts and tools. Each script is self-contained and solves a simple, practical problem.
---
## 🧩 Included Tools
### 🎵 Music Downloader — `music_downloader.py`
A simple Tkinter GUI tool to search and download music from an online source.
- 🔍 **Search** songs by keyword
- ⬇️ **Download** a song by its ID
- 🔥 **Hot songs** list browsing
- 📂 **Custom save path** via file dialog
```bash
python music_downloader.py
```
### 🔐 Argon2 Password Hasher — `argon2_password_hasher.py`
A secure password hashing utility using **Argon2id** (2025 recommended parameters).
- 🧂 **Auto salt** generation
- 💾 **Hash** passwords (Argon2id format)
- ✔️ **Verify** passwords
- 🔄 **Rehash check** for parameter updates
```bash
python argon2_password_hasher.py
```
---
## 📁 Project Structure
```
Messy-Little-Gadgets/
├── music_downloader.py # Music download GUI tool
├── argon2_password_hasher.py # Argon2 password hasher
└── README.md # This document
```
---
## 📄 License
This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for details.
---
## 🙏 Note
> Each gadget is independent and minimal. More little tools will be added over time. Feel free to use them in your own projects.
+60
View File
@@ -0,0 +1,60 @@
from argon2 import PasswordHasher, exceptions
import secrets
class Argon2Hasher:
def __init__(self):
"""
Recommended Argon2id configuration parameters (2025 standard):
- time_cost: Number of iterations (CPU cost)
- memory_cost: Memory usage in KB
- parallelism: Number of parallel threads
- hash_len: Hash output length
- salt_len: Salt length
"""
self.ph = PasswordHasher(
time_cost=3, # Modern hardware: 2-3, older hardware: 1
memory_cost=65536, # 64 MB (in KB)
parallelism=4, # 4 threads
hash_len=32, # 32 bytes = 256 bits
salt_len=16, # 16 bytes = 128 bits
)
def hash_password(self, password: str) -> str:
"""
Hash a password (automatically generates a random salt)
Returns format: $argon2id$v=19$m=65536,t=3,p=4$salt$hash
"""
return self.ph.hash(password)
def verify_password(self, hashed_password: str, password: str) -> bool:
"""
Verify a password against its hash
"""
try:
return self.ph.verify(hashed_password, password)
except (exceptions.VerifyMismatchError, exceptions.VerificationError):
return False
def needs_rehash(self, hashed_password: str) -> bool:
"""
Check if the password needs to be rehashed (used when parameters are updated)
"""
return self.ph.check_needs_rehash(hashed_password)
# Usage example
if __name__ == "__main__":
hasher = Argon2Hasher()
# 1. Hash a password
password = "Lp324123" # This is just an example; use a stronger password in production!
hashed = hasher.hash_password(password)
print(f"Hashed: {hashed}")
# 2. Verify the password
test_password = "Lp324123"
is_valid = hasher.verify_password(hashed, test_password)
print(f"Password valid: {is_valid}")
# 3. Check if rehashing is needed
needs_rehash = hasher.needs_rehash(hashed)
print(f"Needs rehash: {needs_rehash}")
+182
View File
@@ -0,0 +1,182 @@
import requests
from bs4 import BeautifulSoup
import tkinter as tk
from tkinter import filedialog
import re
import os
class MusicDownloader:
def __init__(self, master):
self.master = master
master.title("Music Downloader")
# Search song section
tk.Label(master, text="Song keyword:").grid(row=0, column=0, padx=10, pady=10)
self.keyword_entry = tk.Entry(master, width=30)
self.keyword_entry.grid(row=0, column=1, padx=10, pady=10)
self.search_button = tk.Button(master, text="Search", command=self.search_song)
self.search_button.grid(row=0, column=2, padx=10, pady=10)
# Text box to show search results
self.result_text = tk.Text(master, height=10, width=50)
self.result_text.grid(row=1, column=0, columnspan=3, padx=10, pady=10)
# Hyperlink hint
self.hint_label = tk.Label(master, text="Not the result you want?", fg="blue", cursor="hand2")
self.hint_label.grid(row=2, column=0, columnspan=10, pady=10)
self.hint_label.bind("<Button-1>", self.show_search_hint)
# Download song section
tk.Label(master, text="Song ID:").grid(row=3, column=0, padx=10, pady=10)
self.song_id_entry = tk.Entry(master, width=30)
self.song_id_entry.grid(row=3, column=1, padx=10, pady=10)
self.download_button = tk.Button(master, text="Download", command=self.download_song)
self.download_button.grid(row=3, column=2, padx=10, pady=10)
# Save path section
tk.Label(master, text="Save path:").grid(row=4, column=0, padx=10, pady=10)
self.save_path_entry = tk.Entry(master, width=30)
self.save_path_entry.grid(row=4, column=1, padx=10, pady=10)
self.browse_button = tk.Button(master, text="Browse", command=self.browse_save_path)
self.browse_button.grid(row=4, column=2, padx=10, pady=10)
# Hot music recommendation section
self.hot_music_button = tk.Button(master, text="Hot Songs", command=self.get_hot_music)
self.hot_music_button.grid(row=5, column=0, padx=10, pady=10)
def search_song(self):
keyword = self.keyword_entry.get()
details = self.get_music_details(keyword)
self.result_text.delete(1.0, tk.END) # clear the text box
for detail in details:
self.result_text.insert(tk.END, detail + "\n")
def get_music_details(self, keyword):
base_url = "https://www.gequbao.com/s/"
search_url = f"{base_url}{keyword}"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0'
}
try:
response = requests.get(search_url, headers=headers)
response.raise_for_status() # check if the request succeeded
soup = BeautifulSoup(response.text, 'html.parser')
# Find all matching <a> tags
music_links = soup.select('div.card.mb-1 a.music-link.d-block')
# Find all matching <span> tags
music_titles = soup.select('span.text-primary.font-weight-bolder.music-title.d-md-inline-block.align-middle span')
# Extract all href attributes, removing the '/music/' prefix
hrefs = [link['href'].replace('/music/', '') for link in music_links]
# Extract all song titles
titles = [title.get_text(strip=True) for title in music_titles]
# Combine titles and links
results = [f"Title: {title}, ID: {href}" for title, href in zip(titles, hrefs)]
return results
except requests.RequestException as e:
print(f"Request failed, please try again later: {e}")
return []
def download_song(self):
in_id = self.song_id_entry.get()
save_path = self.save_path_entry.get()
# Check whether the save path exists
if not save_path or not os.path.exists(save_path):
tk.messagebox.showwarning("Warning", "Save path does not exist. Please choose a valid path.")
return
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0'
}
url = f"https://www.gequbao.com/music/{in_id}"
try:
resp = requests.get(url, headers=headers, timeout=5) # set timeout to 5 seconds
except requests.exceptions.Timeout:
tk.messagebox.showwarning("Warning", "Request timed out. Please check your network.")
return
except requests.exceptions.RequestException as e:
tk.messagebox.showwarning("Warning", f"Request failed, please try again later: {e}")
return
link = "https://www.gequbao.com/api/play-url"
data = {'id':'RFALUCRUAVZeSVsNV3NRXF5fF1pdAnhXU10DE1gPU3IaUVtRQlFNVndQUFpWRF9YUQ=='}
try:
response = requests.post(url=link, data=data, headers=headers, timeout=5) # set timeout to 5 seconds
json_data = response.json()
play_url = json_data['data']['url']
except requests.exceptions.Timeout:
tk.messagebox.showwarning("Warning", "Request timed out. Please check your network.")
return
except requests.exceptions.RequestException as e:
tk.messagebox.showwarning("Warning", f"Request failed, please try again later: {e}")
return
try:
content = requests.get(play_url, headers=headers, timeout=5).content # set timeout to 5 seconds
except requests.exceptions.Timeout:
tk.messagebox.showwarning("Warning", "Request timed out. Please check your network.")
return
except requests.exceptions.RequestException as e:
tk.messagebox.showwarning("Warning", f"Request failed, please try again later: {e}")
return
# Save the song
file_path = os.path.join(save_path, f"ruyuan.mp3")
with open(file_path, 'wb') as f:
f.write(content)
self.result_text.insert(tk.END, f'Song downloaded successfully\n')
def browse_save_path(self):
path = filedialog.askdirectory()
self.save_path_entry.delete(0, tk.END)
self.save_path_entry.insert(0, path)
def get_hot_music(self):
# clear the text box
self.result_text.delete(1.0, tk.END)
base_url = "https://www.gequbao.com/hot-music/"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0'
}
results = []
for page in range(1, 7):
url = f"{base_url}{page}"
try:
response = requests.get(url, headers=headers)
response.raise_for_status() # check if the request succeeded
soup = BeautifulSoup(response.text, 'html.parser')
# Find all matching <td> tags (singers)
singers = [td.get_text(strip=True) for td in soup.select('td.text-success')]
# Find all matching <a> tags
music_links = soup.select('a.text-info.font-weight-bold')
titles = [a.get_text(strip=True) for a in music_links]
hrefs = [a['href'].replace('/music/', '') for a in music_links]
# Combine title, singer and ID
page_results = [f"Title: {title}, Singer: {singer}, ID: {href}" for title, singer, href in zip(titles, singers, hrefs)]
results.extend(page_results)
except requests.RequestException as e:
tk.messagebox.showwarning("Warning", f"Request failed, please try again later: {e}")
return
for result in results:
self.result_text.insert(tk.END, result + "\n")
def show_search_hint(self, event):
tk.messagebox.showinfo("Search Hint", "Avoid special characters when searching. Enter the name and author together for better matching (e.g. ru yuan Wang Fei)")
if __name__ == "__main__":
root = tk.Tk()
app = MusicDownloader(root)
root.mainloop()