├── randpasswd ├── requirements.txt ├── LICENCE └── genpass.py ├── musiccrawler ├── requirements.txt ├── urls.txt ├── LICENCE └── main.py ├── .gitignore └── ReadMe.md /randpasswd/requirements.txt: -------------------------------------------------------------------------------- 1 | maskpass>=0.3.7 2 | cryptography>=39.0.0 -------------------------------------------------------------------------------- /musiccrawler/requirements.txt: -------------------------------------------------------------------------------- 1 | # use python 3.10 2 | aiohttp[speedups] ~= 3.8.5 3 | aiofiles~=23.1.0 4 | beautifulsoup4 ~= 4.12.2 -------------------------------------------------------------------------------- /musiccrawler/urls.txt: -------------------------------------------------------------------------------- 1 | https://musicguitars.ir/%da%af%d9%84%da%86%db%8c%d9%86-%d8%a2%d9%87%d9%86%da%af-%d9%87%d8%a7%db%8c-%d8%af%d8%a7%d8%b1%db%8c%d9%88%d8%b4-%d8%a7%d9%82%d8%a8%d8%a7%d9%84%db%8c-4/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | test_genpass.py 3 | passgen.enc 4 | secret.key 5 | __pycache__ 6 | .coverage 7 | .pytest_cache 8 | htmlcov 9 | todo.md 10 | *.prof 11 | musics 12 | best_of_*.json 13 | *.ipynb 14 | -------------------------------------------------------------------------------- /musiccrawler/LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mohammad Abbasi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /randpasswd/LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mohammad Abbasi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # Some custom daily tools 2 | 3 |
4 |

Random Password Generator

5 | 6 | - [x] **genpass**: _an easy-to-use free random password generator, never use `forget password` again_. 7 | - _Give it a name (e.g. server name), It will generate a new highly secure password and will save it on your system as an encrypted file so you will never forget your password again._ 8 | 9 | - _On first use it will generate a key file for you which you must keep safe (**please don't save it alongside your `passgen.enc`**), this key will be used for retrieving data from the encrypted file or saving a new password in it._ 10 | - `CAUTION`: _As an example, you can save `genpass` or the encrypted file on your `phone/tablet/pc/USB` and keep the `1-time` generated key file on your `laptop`._ **Keep both safe or you will lose all your passwords** 11 | 12 | **USAGE:** 13 | 14 | - _first time use:_ 15 | 16 | ```bash 17 | $ python3 genpass.py --init 18 | ----OR---- 19 | # to specify encryption key path 20 | $ python3 genpass --init -i ./Documents/passgen.enc -k ./Desktop/secret.key 21 | ----OR---- 22 | # initialize and generate password all together 23 | $ python3 genpass --init -n MyEmail 24 | ``` 25 | 26 | _The above command will generate an encrypted file in `Documents` with the name of `passgen` and an encryption key in `Desktop` named `secret.key`._ 27 | 28 | > `-f (optional):` _Path and filename for encrypted file (default current directory/folder)._ 29 | > 30 | > `-i (optional):` _Path and filename to save key (default current directory/folder)._ 31 | > 32 | > `-n:` _A name for a new password for easier retrieval or remember (names cannot have space use` _`)\_ 33 | 34 | - _after that:_ 35 | 36 | ```bash 37 | # generate new password 38 | $ python3 genpass.py -n myEmail 39 | ---- OR ---- 40 | $ python3 genpass.py -k ./secret.key -i ./Document/passgen.enc -n "myEmail" -l 20 -e 41 | ---- OR ---- 42 | # List all names 43 | $ python3 genpass.py -a 44 | ---- OR ---- 45 | # find for specific name 46 | $ python3 genpass.py -k ./secret.key -f "myEmail" 47 | ---- OR ---- 48 | # generate password without saving 49 | $ python3 genpass.py -l 20 50 | ``` 51 | 52 | > `-n:` _A name for a new password for easier retrieval or remember (names cannot have space use` _`)\_ 53 | > 54 | > `-k (optional):` _Path and filename to key, **auto-created in the first use** (default current directory/folder)._ 55 | > 56 | > `-i (optional):` _Path and filename for encrypted file (default current directory/folder)_ 57 | > `-l (optional):` _length of a new random password $12$ or higher (default: $12$)_ 58 | > 59 | > `-f (optional)`: _Find the password for the provided name_ 60 | > 61 | > `-e (optional):` _if provided password may include `+=-_,.|\/{}()[]<>` characters.\_ 62 | > 63 | > `-a (optional):` _list all names used for saved passwords._ 64 | 65 | _`Test environment`: `OS Linux`, `Python 3.8.10`_ 66 | 67 | **Recommendation:** \_make an alias in `~/.bashrc` for easier use. 68 | 69 |
70 | 71 |
72 |

MusicGuitar Bestof collection crawler (Async Python)

73 | 74 | - [x] **musiccrawler**: _an easy-to-use music crawler written in Python with `asyncio`_ 75 | 76 | _Can be used as a tutorial for `asyncio, aiohttp, aiofiles`_ 77 | 78 | - _after crawling the project open `musiccrawler` directory and update `urls.txt` with the URL to the best collection of your favorite singer split by a new line._ 79 | 80 | - _only support URLs from [musicguitars.ir](https://musicguitars.ir/%da%af%d9%84%da%86%db%8c%d9%86-%d8%a2%d9%87%d9%86%da%af-%d9%87%d8%a7%db%8c-%d8%af%d8%a7%d8%b1%db%8c%d9%88%d8%b4-%d8%a7%d9%82%d8%a8%d8%a7%d9%84%db%8c-4/)_ 81 | 82 | - _to run the script you can use_ 83 | 84 | ```bash 85 | $ python main.py 128 86 | ``` 87 | 88 | > _$128$ is the music file quality (by default set to $320$)_ 89 | 90 | _`Test environment`: `OS Linux`, `Python 3.10.12`_ 91 | 92 |
93 | -------------------------------------------------------------------------------- /musiccrawler/main.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import asyncio 3 | import os, json, sys 4 | from aiohttp import ClientSession 5 | from aiofiles import open as aopen 6 | from bs4 import BeautifulSoup 7 | from collections import defaultdict 8 | 9 | 10 | class Colors: 11 | OKGREEN = "\033[32m" 12 | WARNING = "\033[33m" 13 | FAIL = "\033[31m" 14 | BOLD = "\033[1m" 15 | END = "\033[0m" 16 | 17 | 18 | def clean_filename(filename): 19 | return ( 20 | filename.replace("%20", "_") 21 | .replace("%5B", "") 22 | .replace("%5D", "") 23 | .replace("_-_", "-") 24 | .replace("_[320]", "_320") 25 | ) 26 | 27 | 28 | def extract_artist_name(meta): 29 | artist_name = meta.split("/")[-1].split(".")[0].lower() 30 | for suffix in [ 31 | "-songs-coll", 32 | "-songs-colletion", 33 | "-songs-collection", 34 | "-song-collection", 35 | "-song-colletion", 36 | "-music-colletion", 37 | "-music-collection", 38 | "-best-music", 39 | "-best-songs", 40 | ]: 41 | artist_name = artist_name.split(suffix)[0] 42 | return artist_name.replace("-", " ").title().replace(" ", "_") 43 | 44 | 45 | async def fetch_main_page(session: ClientSession, url: str): 46 | async with session.get(url) as response: 47 | assert response.status == 200 48 | return await response.content.read() 49 | 50 | 51 | async def fetch_music(session: ClientSession, artist_dir: str, url: str): 52 | async with session.get(url) as response: 53 | assert response.status == 200 54 | fname = clean_filename(url.split("/")[-1]) 55 | file_path = os.path.join(artist_dir, fname) 56 | async with aopen(file_path, "wb") as f: 57 | await f.write(await response.read()) 58 | 59 | 60 | async def main(quality): 61 | main_dir = os.path.dirname(os.path.abspath(__file__)) 62 | urls_file = os.path.join(main_dir, "urls.txt") 63 | try: 64 | print(f"{Colors.OKGREEN}Reading best of urls file ...{Colors.END}") 65 | with open(urls_file, "rt", encoding="utf-8") as f: 66 | urls = tuple(set(f.readlines())) 67 | except FileNotFoundError as ex: 68 | print(f"{Colors.FAIL}{urls_file} does not exist{Colors.END}\n") 69 | sys.exit(1) 70 | 71 | best_of_json = defaultdict(list) 72 | async with aiohttp.ClientSession() as session: 73 | print(f"{Colors.OKGREEN}start sending requests ...{Colors.END}\n") 74 | all_requests = [fetch_main_page(session, url) for url in urls] 75 | for finished_task in asyncio.as_completed(all_requests): 76 | content = await finished_task 77 | try: 78 | content = BeautifulSoup(content, "html.parser") 79 | meta = content.select_one("meta[property='og:image']")["content"] 80 | artist_name = extract_artist_name(meta) 81 | print( 82 | f"{Colors.WARNING}start sending requests for {artist_name} urls ...{Colors.END}" 83 | ) 84 | match quality: 85 | case "320": 86 | tracks_urls = [ 87 | url.select_one("a.icon.dwl")["href"] 88 | for url in content.select(".atn~ td+ td") 89 | ] 90 | case "128": 91 | tracks_urls = [ 92 | url.select_one("a.icon.dwl")["href"] 93 | for url in content.select(".atn+ td") 94 | ] 95 | artist_dir = os.path.join(main_dir, f"musics/{artist_name}") 96 | os.makedirs(artist_dir, exist_ok=True) 97 | all_musics = [ 98 | fetch_music(session, artist_dir, url) for url in tracks_urls 99 | ] 100 | try: 101 | await asyncio.gather(*all_musics, return_exceptions=True) 102 | except Exception as ex: 103 | print(ex) 104 | 105 | best_of_json["artists"].append( 106 | {"name": artist_name, "musics_urls": tracks_urls} 107 | ) 108 | 109 | except Exception as ex: 110 | print(ex) 111 | 112 | out_json = os.path.join(main_dir, "best_of_urls_downloaded.json") 113 | with open(out_json, "wt") as jf: 114 | jf.write(json.dumps(best_of_json, indent=2)) 115 | 116 | 117 | if __name__ == "__main__": 118 | try: 119 | quality = sys.argv[1] 120 | if quality not in ["128", "320"]: 121 | print( 122 | f"\n{Colors.WARNING}Invalid quality value. Switching to default (320).{Colors.END}" 123 | ) 124 | quality = "320" 125 | except IndexError: 126 | quality = "320" 127 | print(f"\n{Colors.WARNING}set quality to {quality}.{Colors.END}") 128 | asyncio.run(main(quality)) 129 | -------------------------------------------------------------------------------- /randpasswd/genpass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os, argparse, random, string, json 3 | from cryptography.fernet import Fernet 4 | 5 | 6 | class PassGen: 7 | def init_files(self, kpath, fpath): 8 | self.generate_key(kpath) 9 | self.write_file(fpath, {}) 10 | 11 | def random_password(self, plen=12, ext=False): 12 | base = string.ascii_letters + """1234567890""" 13 | special = """!@#$%^&*?~`'":;""" 14 | extend = r"+=-_,.|\}{)(][/><" 15 | special_chr = random.choice(special) 16 | passwd = "".join(random.choices(base, k=plen - 1)) 17 | if ext: 18 | passwd = "".join(random.choices(base + extend, k=plen - 1)) 19 | return passwd + special_chr 20 | return passwd + special_chr 21 | 22 | def generate_key(self, path): 23 | if os.path.exists(path): 24 | raise ValueError( 25 | f"File already exists {path!r} \033[33mconsider removing --init\033[0m" 26 | ) 27 | key = Fernet.generate_key() 28 | with open(path, "wb") as f: 29 | f.write(key) 30 | return Fernet(key) 31 | 32 | def load_key(self, path): 33 | with open(path, "rb") as f: 34 | k = f.read() 35 | return Fernet(k) 36 | 37 | def load_file(self, fpath): 38 | with open(fpath, "r") as f: 39 | return json.load(f) 40 | 41 | def write_file(self, fpath, data): 42 | with open(fpath, "w") as f: 43 | json.dump(data, f) 44 | 45 | def read_password(self, kpath, fpath, name): 46 | fernet = self.load_key(kpath) 47 | pwdict = self.load_file(fpath) 48 | if name in pwdict: 49 | found_pass = fernet.decrypt(bytes(pwdict[name], "utf-8")) 50 | return str(found_pass, encoding="utf-8") 51 | return "No result for {name}" 52 | 53 | def write_password(self, kpath, fpath, name, plen=12, ext=False): 54 | raw_pass = self.random_password(plen, ext) 55 | if not name: 56 | return raw_pass 57 | fernet = self.load_key(kpath) 58 | pw = str(fernet.encrypt(raw_pass.encode("utf-8")), encoding="utf-8") 59 | pwdict = self.load_file(fpath) 60 | if name not in pwdict: 61 | pwdict[name] = pw 62 | self.write_file(fpath, pwdict) 63 | return raw_pass 64 | else: 65 | while True: 66 | act = input( 67 | "\033[1mName already exists do you wan't to overwrite it? (Y/N)\033[0m" 68 | ) 69 | if act == "Y": 70 | pwdict[name] = pw 71 | self.write_file(fpath, pwdict) 72 | return raw_pass 73 | elif act == "N": 74 | return "Stopped by user request." 75 | 76 | def find_password(self, name, kpath, fpath): 77 | fernet = self.load_key(kpath) 78 | pwdict = self.load_file(fpath) 79 | if passwd := pwdict.get(name, None): 80 | return str(fernet.decrypt(bytes(passwd, "utf-8")), encoding='utf-8') 81 | return passwd 82 | 83 | def get_all_names(self, fpath): 84 | pwdict = self.load_file(fpath) 85 | rows, cols = [], [] 86 | for idx, k in enumerate(pwdict.keys()): 87 | cols.append(k) 88 | if (idx + 1) % 4 == 0: 89 | rows.append("{:>15}{:>15}{:>15}{:>15}".format(*cols)) 90 | cols = [] 91 | rows.append(("{:>15}" * (len(cols))).format(*cols)) 92 | return "\n\033[1;34mList of keys are:\n\033[33m" + "\n".join(rows) 93 | 94 | 95 | def main(args): 96 | # create new secret and .enc 97 | if args.init: 98 | passgen.init_files(args.k, args.i) 99 | if args.n is None: 100 | return "\033[34mKey and encryption files created.\033[0m" 101 | # # generate and save password after file creatation 102 | elif args.l < 12: 103 | raise ValueError("Password length cannot be less than 12 characters") 104 | raw_pass = passgen.write_password(args.k, args.i, args.n, args.l, args.e) 105 | return f"\n\033[1mYour password is: \033[32m{raw_pass}\033[0m" 106 | # List all names 107 | if args.a: 108 | return passgen.get_all_names(args.i) 109 | # Find password for given name 110 | if args.f: 111 | raw_pass = passgen.find_password(args.f, args.k, args.i) 112 | if raw_pass: 113 | return f"\n\033[1mYour password is: \033[32m{raw_pass}\033[0m" 114 | return f"\n\033[33mNo saved password for {args.f}\033[0m" 115 | if args.l < 12: 116 | raise ValueError("Password length cannot be less than 12 characters") 117 | # create one time password 118 | if args.n is None: 119 | raw_pass = passgen.random_password(args.l, args.e) 120 | return f"\n\033[1;33mOne time password (no saving): \033[32m{raw_pass}\033[0m" 121 | # generate and save password 122 | raw_pass = passgen.write_password(args.k, args.i, args.n, args.l, args.e) 123 | return f"\n\033[1mYour password is: \033[32m{raw_pass}\033[0m" 124 | 125 | 126 | def parse_args(): 127 | parser = argparse.ArgumentParser(description="Random Password Generator") 128 | parser.add_argument( 129 | "--init", 130 | action="store_true", 131 | help="Initialize and generate encrypted file and key", 132 | ) 133 | parser.add_argument( 134 | "-n", 135 | type=str, 136 | help="A name for new password, will help you remember and retrieve password easier.", 137 | ) 138 | parser.add_argument( 139 | "-f", 140 | required=False, 141 | help="Find password for provided name", 142 | ) 143 | parser.add_argument( 144 | "-a", 145 | action="store_true", 146 | required=False, 147 | help="List all names used to retrieve passwords", 148 | ) 149 | parser.add_argument( 150 | "-k", 151 | required=False, 152 | default=os.path.join(os.getcwd(), "secret.key"), 153 | help="Path to the key (include it's name). default current dir", 154 | ) 155 | parser.add_argument( 156 | "-i", 157 | required=False, 158 | default=os.path.join(os.getcwd(), "passgen.enc"), 159 | help="Path to the encrypted file (include it's name). default current dir", 160 | ) 161 | parser.add_argument( 162 | "-l", 163 | required=False, 164 | type=int, 165 | default=12, 166 | help="Length of new random password 8 or higher (default:8)", 167 | ) 168 | parser.add_argument( 169 | "-e", 170 | required=False, 171 | action="store_true", 172 | help="If provided password may include +=-_,.|\/{}()[]<> characters.", 173 | ) 174 | return parser.parse_args() 175 | 176 | 177 | if __name__ == "__main__": 178 | try: 179 | passgen = PassGen() 180 | args = parse_args() 181 | print(main(args)) 182 | 183 | except Exception as e: 184 | print(f"\n\033[31m{e}\033[0m") 185 | --------------------------------------------------------------------------------