├── 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 |
--------------------------------------------------------------------------------