Skip to content

Commit ea88590

Browse files
committed
logo + fix
1 parent ea07540 commit ea88590

File tree

10 files changed

+60
-47
lines changed

10 files changed

+60
-47
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# hashtray
22

33
<p align="center">
4-
<img src="https://raw.githubusercontent.com/balestek/hashtray/master/media/hashtray.png">
4+
<img src="https://raw.githubusercontent.com/balestek/hashtray/master/media/hashtray-logo.png">
55
</p>
66

77
[![PyPI version](https://badge.fury.io/py/hashtray.svg)](https://badge.fury.io/py/hashtray)
@@ -67,6 +67,10 @@ pip install hashtray
6767

6868
### Find Gravatar account with an email
6969

70+
<p align="center">
71+
<img src="https://raw.githubusercontent.com/balestek/hashtray/master/media/hashtray-email.png">
72+
</p>
73+
7074
Pretty straightforward. The command is `email` .
7175

7276
It converts the email address into its MD5 hash. _hashtray_ then checks if a public profile associated with the hash exists on Gravatar. If found, it displays the profile information.
@@ -81,6 +85,10 @@ In such cases, _hashtray_ alerts you. You can then attempt to find the primary e
8185

8286
### Find an email from a Gravatar username or hash
8387

88+
<p align="center">
89+
<img src="https://raw.githubusercontent.com/balestek/hashtray/master/media/hashtray-account.png">
90+
</p>
91+
8492
To find an email address associated with a Gravatar username or hash, use the account command.
8593

8694
hashtray creates a list of possible email addresses using data from the Gravatar profile.

hashtray/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.1b4"
1+
__version__ = "0.0.1"

hashtray/cli.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from rich.console import Console
55

6+
from hashtray.__about__ import __version__ as version
67
from hashtray.email_enum import EmailEnum
78
from hashtray.gravatar import Gravatar
89

@@ -61,20 +62,19 @@ def parse_app_args(arguments=None):
6162

6263
def main() -> None:
6364
c.print(
64-
r"""
65-
██╗░░██╗░█████╗░░██████╗██╗░░██╗████████╗██████╗░░█████╗░██╗░░░██╗
66-
██║░░██║██╔══██╗██╔════╝██║░░██║╚══██╔══╝██╔══██╗██╔══██╗╚██╗░██╔╝
67-
███████║███████║╚█████╗░███████║░░░██║░░░██████╔╝███████║░╚████╔╝░
68-
██╔══██║██╔══██║░╚═══██╗██╔══██║░░░██║░░░██╔══██╗██╔══██║░░╚██╔╝░░
69-
██║░░██║██║░░██║██████╔╝██║░░██║░░░██║░░░██║░░██║██║░░██║░░░██║░░░
70-
╚═╝░░╚═╝╚═╝░░╚═╝╚═════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░
71-
[white]j m . b a l e s t e k[/white]
65+
f"""[bold]
66+
[turquoise2]⠀⠀⣠⣴⣶⠿⠿⣷⣶⣄⠀⠀⠀[/turquoise2]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
67+
[turquoise2]⠀⢾⠟⠉⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀[/turquoise2]⣤⡄⠀⣤⠀⠀⣤⣄⠀⢀⣤⠤⠤⠀⣤⠀⢠⡄⠤⢤⣤⠤⢠⣤⠤⣤⠀⠀⣠⣤⠀⠠⣤⠀⢠⡄
68+
[turquoise2]⠀⠀⠀⠀⠀⠀⣶⣶⣶⣶⣶⣶⠂⠀[/turquoise2]⣿⣇⣀⣿⠀⢰⡟⢿⡀⠸⣧⣀⠀⠀⣿⣀⣸⡇⠀⢸⡇⠀⢸⡇⠀⣹⡇⢀⣿⢻⡇⠀⢹⣦⡿⠁
69+
[turquoise2]⣀⣀⠀⠀⠀⠀⠋⠃⠀⠀⢸⣿⠀⠀[/turquoise2]⣿⡏⠉⣿⠀⣾⠧⢾⣇⠀⠈⠙⣿⠀⣿⠉⢹⡇⠀⢸⡇⠀⢸⡿⢺⣟⠀⣸⡷⠼⣿⠀⠀⣿⠃⠀
70+
[turquoise2]⠘⢿⣦⣀⠀⠀⠀⠀⢀⣴⣿⠋⠀⠀[/turquoise2]⠛⠃⠀⠛⠐⠛⠀⠀⠛⠐⠶⠶⠛⠀⠛⠀⠘⠃⠀⠘⠃⠀⠘⠓⠀⠛⠂⠛⠀⠀⠙⠃⠀⠛⠀⠀
71+
[turquoise2]⠀⠀⠙⠻⠿⣶⣶⡿⠿⠋⠁ [/turquoise2][/bold]jm balestek⠀v{version}⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀
7272
""",
73-
style="#1e8cbe",
7473
)
7574
c.print(
7675
"[bold turquoise2]Gravatar Account and Email Finder[/bold turquoise2]\n"
77-
"Find a Gravatar account from an email address or find an email address from a Gravatar account or hash.\n"
76+
"Find a Gravatar account from an email address or\n"
77+
"find an email address from a Gravatar account or hash.\n"
7878
"\n"
7979
":arrow_forward: [bold turquoise2]Find a gravatar account from en email:[/bold turquoise2]\n"
8080
" [bright_white]Usage:[/bright_white] [gold1]hashtray[/gold1] [orange_red1]email[/orange_red1] email@example.com\n"
@@ -85,9 +85,9 @@ def main() -> None:
8585
" [bright_white]Options:[/bright_white]\n"
8686
" [orange3]--domain_list, -l[/orange3] [tan]common|long|full[/tan]\n"
8787
" Domain list to use for email enumeration. Default: common\n"
88-
" [orange3]--elements, -e[/orange3] [tan]element1 element2,...[/tan]\n"
88+
" [orange3]--elements, -e[/orange3] [tan]element1 element2 ...[/tan]\n"
8989
" Generate combinations with your elements/strings instead\n"
90-
" [orange3]--domains, -d[/orange3] [tan]domain1.com domain2.com,...[/tan]\n"
90+
" [orange3]--domains, -d[/orange3] [tan]domain1.com domain2.com ...[/tan]\n"
9191
" Use your custom email domains for emails generation\n"
9292
" [orange3]--crazy, -c[/orange3] Go crazy and try EVERY SINGLE combination\n"
9393
" (with any special character at any place in the combinations)\n"

hashtray/data/email_services_full.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@
453453
"vnn.vn",
454454
"aver.com",
455455
"bestweb.net",
456-
"chickenkiller.com"
456+
"chickenkiller.com",
457457
"keemail.com",
458458
"posteo.us",
459459
"posteo.uk",

hashtray/email_enum.py

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def __init__(
2222
custom_domains: list = None,
2323
crazy: bool = False,
2424
):
25-
self.account_hash = self.hashed = account_hash
25+
self.account_hash = account_hash
26+
self.hashed = None
2627
self.separators = ["", ".", "_", "-"]
2728
self.name_pattern = "[-_ ./]"
2829
self.emails = []
@@ -42,7 +43,7 @@ def __init__(
4243
self.n_combs = 0
4344
self.c = c(highlight=False)
4445

45-
def load_domains(self):
46+
def load_domains(self) -> json:
4647
# Load domains from json files
4748
domain_files = {None: "", "common": "", "long": "_long", "full": "_full"}
4849
domain_file = domain_files[self.domain_list]
@@ -65,13 +66,14 @@ def create_elements(self) -> list:
6566
)
6667
sys.exit(1)
6768
# Get elements with custom arguments or from the Gravatar profile
69+
self.hashed = self.g.info()["hash"]
6870
if self.elements:
6971
elements = self.get_custom_elements()
7072
else:
7173
elements = self.get_elements_from_gravatar()
7274
return elements
7375

74-
def get_custom_elements(self):
76+
def get_custom_elements(self) -> list:
7577
# Get elements from custom arguments
7678
self.elements = [
7779
unidecode(element.lower())
@@ -85,19 +87,18 @@ def get_custom_elements(self):
8587
)
8688
return elements
8789

88-
def get_elements_from_gravatar(self):
90+
def get_elements_from_gravatar(self) -> list:
8991
# Get elements from the Gravatar profile
9092
elements = []
9193
infos = self.g.info()
9294
self.get_public_emails(infos)
93-
self.hashed = infos["hash"]
9495
gob = self.process_gravatar_info(infos)
9596
for element in gob:
9697
deco = unidecode(element.lower())
9798
elements.append(deco) if deco not in elements else None
9899
return elements
99100

100-
def get_public_emails(self, infos):
101+
def get_public_emails(self, infos: dict) -> None:
101102
# Get emails from the Gravatar json emails
102103
if infos["emails"]:
103104
self.public_emails.extend(
@@ -110,7 +111,7 @@ def get_public_emails(self, infos):
110111
find = re.findall(pattern, infos["aboutMe"]) if infos["aboutMe"] else None
111112
self.public_emails.extend(find) if find else None
112113

113-
def process_gravatar_info(self, infos):
114+
def process_gravatar_info(self, infos: dict) -> list:
114115
# Process Gravatar infos to get additional chunks
115116
gob = []
116117
self.add_preferred_username(infos, gob)
@@ -122,44 +123,44 @@ def process_gravatar_info(self, infos):
122123
self.add_elements(gob)
123124
return gob
124125

125-
def add_preferred_username(self, infos, gob):
126+
def add_preferred_username(self, infos: dict, gob: list) -> None:
126127
if infos["preferredUsername"]:
127128
gob.append(infos["preferredUsername"])
128129

129-
def add_profile_url(self, infos, gob):
130+
def add_profile_url(self, infos: dict, gob: list) -> None:
130131
if infos["profileUrl"]:
131132
gob.append(self.last_url_chunk(infos["profileUrl"]))
132133

133-
def add_chunks(self, string, gob):
134+
def add_chunks(self, string: str, gob: list) -> None:
134135
if string:
135136
names = re.split(self.name_pattern, unidecode(string))
136137
chunks = [name for name in names if name]
137138
# Add first letter for given and family names
138139
chunks.extend(name[:1] for name in names if len(name) > 1)
139140
gob.extend(chunks)
140141

141-
def add_display_name(self, infos, gob):
142+
def add_display_name(self, infos: dict, gob: list) -> None:
142143
names = re.split(self.name_pattern, unidecode(infos["displayName"]))
143144
gob.extend(name for name in names if name)
144145

145-
def add_given_name(self, infos, gob):
146+
def add_given_name(self, infos: dict, gob: list) -> None:
146147
# Add given name and first letter chunks
147148
if infos["name"] and infos["name"]["givenName"]:
148149
self.add_chunks(infos["name"]["givenName"], gob)
149150

150-
def add_family_name(self, infos, gob):
151+
def add_family_name(self, infos: dict, gob: list) -> None:
151152
# Add family name and first letter chunks
152153
if infos["name"] and infos["name"]["familyName"]:
153154
self.add_chunks(infos["name"]["familyName"], gob)
154155

155-
def add_accounts(self, infos, gob):
156+
def add_accounts(self, infos: dict, gob: list) -> None:
156157
# Add account chunks for verified accounts
157158
if infos["accounts"]:
158159
for account in infos["accounts"]:
159160
account_url = infos["accounts"][account].rstrip("/")
160161
self.process_account(account, account_url, gob)
161162

162-
def process_account(self, account, account_url, gob):
163+
def process_account(self, account: str, account_url: str, gob: list) -> None:
163164
# Verified accounts username chunks
164165
if account in ["Mastodon", "Fediverse"]:
165166
gob.append(self.last_url_chunk(account_url).replace("@", ""))
@@ -200,7 +201,7 @@ def process_account(self, account, account_url, gob):
200201
chunk = self.last_url_chunk(account_url)
201202
gob.append(chunk)
202203

203-
def add_elements(self, gob):
204+
def add_elements(self, gob: list) -> None:
204205
# Building final list of chunks, deduped , lowercase and unidecoded if it hasn't been yet
205206
self.elements = []
206207
self.elements = [
@@ -209,27 +210,27 @@ def add_elements(self, gob):
209210
if unidecode(element.lower()) not in self.elements
210211
]
211212

212-
def hash_email(self, email):
213+
def hash_email(self, email: str) -> str:
213214
# MD5 hashing of a string email
214215
return hashlib.md5(email.lower().encode()).hexdigest()
215216

216-
def check_mailhash(self, s: str):
217+
def check_mailhash(self, s: str) -> bool:
217218
# Check if a string is a valid MD5 hash
218219
return re.fullmatch(r"[a-fA-F0-9]{32}", s) is not None
219220

220-
def check_email(self, email):
221+
def check_email(self, email: str) -> bool:
221222
# Check if a string is a valid email
222223
return (
223224
True
224225
if re.match(r"(^[a-zA-Z0-9_.%+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email)
225226
else False
226227
)
227228

228-
def last_url_chunk(self, s: str):
229+
def last_url_chunk(self, s: str) -> str:
229230
# Get the last chunk of an URL
230231
return s.split("/")[-1]
231232

232-
def is_combination(self, s, chunks):
233+
def is_combination(self, s: str, chunks: list) -> bool:
233234
# Logic to check if a string is a combination of other strings
234235
if s in chunks:
235236
chunks.remove(s)
@@ -244,18 +245,18 @@ def is_combination(self, s, chunks):
244245
return True
245246
return False
246247

247-
def dedup_chunks(self, chunks):
248+
def dedup_chunks(self, chunks: list) -> list:
248249
# Remove chunks from a list of chunks if it's made with other strings of the list
249250
return [s for s in chunks if not self.is_combination(s, chunks.copy())]
250251

251-
def show_chunks(self, elements):
252+
def show_chunks(self, elements: list) -> str:
252253
# Show chunks as a string
253254
em = ""
254255
for element in elements:
255256
em += element + ", "
256257
return em.rstrip(", ")
257258

258-
def get_combination_count(self, n):
259+
def get_combination_count(self, n: int) -> int:
259260
# Calculate the total number of combinations for tdqm bar progress
260261
total = 0
261262
for r in range(1, n + 1):
@@ -279,7 +280,7 @@ def get_combination_count(self, n):
279280
# Multiply by the number of domains
280281
return total * len(self.domains)
281282

282-
def combinator(self):
283+
def combinator(self) -> str:
283284
# Generate all possible email combinations for unique elements
284285
# Get chunks and dedup them if made with other chunks
285286
elements = self.dedup_chunks(self.create_elements())
@@ -323,7 +324,7 @@ def combinator(self):
323324
email_local_part = separator.join(permutation)
324325
yield f"{email_local_part}@{domain}"
325326

326-
def hashes(self):
327+
def hashes(self) -> str:
327328
# Calculate emails hash
328329
for email in self.combinator():
329330
hashd = self.hash_email(email)
@@ -337,7 +338,7 @@ def hashes(self):
337338
return email
338339
self.bar.close()
339340

340-
def find(self):
341+
def find(self) -> None:
341342
# Print process and results
342343
self.c.print(
343344
f"[turquoise2]Finding email for [bold]{self.account_hash}[/bold][/turquoise2]\n"
@@ -382,5 +383,8 @@ def find(self):
382383
"\n[light_pink1]:question_mark: Do you want to display the profile info? (y/n):[/light_pink1]"
383384
)
384385
if show_profile.lower() == "y":
386+
print(
387+
f"------------------------------------------------------------------------------------\n"
388+
)
385389
self.g.print_info()
386390
exit(0)

hashtray/gravatar.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,15 @@ def __init__(self, email=None, ghash: str = None, account: str = None):
7474
highlight=False, theme=Theme({"repr.url": "not underline bold white"})
7575
)
7676

77-
def check_email(self):
77+
def check_email(self) -> bool:
7878
# Check if email is valid
7979
if re.match(r"(^[a-zA-Z0-9_.%+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", self.email):
8080
return True
8181
else:
8282
self.c.print(f"[red]Invalid email address: {self.email}.[/red]")
8383
exit(0)
8484

85-
def get_json(self):
85+
def get_json(self) -> dict:
8686
# Get Gravatar json data
8787
try:
8888
res = httpx.get(self.account_url + ".json")
@@ -91,7 +91,7 @@ def get_json(self):
9191
except Exception as e:
9292
print(e)
9393

94-
def process_list(self, info, data):
94+
def process_list(self, info: list, data: dict) -> dict:
9595
# Build a dictionary with the json data
9696
info_dict = {}
9797
for i, item in enumerate(data[info]):
@@ -113,7 +113,7 @@ def process_list(self, info, data):
113113
info_dict[key] = item[key]
114114
return info_dict
115115

116-
def info(self):
116+
def info(self) -> dict:
117117
# Get the Gravatar info if it's a list or not
118118
try:
119119
data = self.get_json()["entry"][0]
@@ -134,11 +134,12 @@ def info(self):
134134
self.infos[info] = data[info]
135135
return self.infos
136136

137-
def print_info(self):
137+
def print_info(self) -> None:
138138
# Print the Gravatar profile infos
139139
data = self.info()
140140
if self.email:
141141
self.c.print(
142+
f"------------------------------------------------------------------------------------\n"
142143
f"Gravatar info for [bold white]{self.email}[/bold white]:\n"
143144
)
144145
for info in data:

media/hashtray-account.png

113 KB
Loading

media/hashtray-email.png

124 KB
Loading

media/hashtray-logo.png

77.1 KB
Loading

media/hashtray.png

-8.37 KB
Binary file not shown.

0 commit comments

Comments
 (0)