-
-
Notifications
You must be signed in to change notification settings - Fork 4k
/
avatar.py
159 lines (137 loc) · 5.07 KB
/
avatar.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
Note: Also see letterAvatar.jsx. Anything changed in this file (how colors are
selected, the svg, etc) will also need to be changed there.
"""
from __future__ import annotations
from collections.abc import MutableMapping
from typing import IO
from urllib.parse import urlencode
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.utils.encoding import force_str
from django.utils.html import escape, format_html
from django.utils.safestring import SafeString
from PIL import Image
from sentry.http import safe_urlopen
from sentry.utils.hashlib import sha256_text
def get_gravatar_url(
email: str | None, size: int | None = None, default: int | str | None = "mm"
) -> str:
if email is None:
email = ""
gravatar_url = "{}/avatar/{}".format(
settings.SENTRY_GRAVATAR_BASE_URL,
sha256_text(email.strip().lower()).hexdigest(),
)
properties: MutableMapping[str, int | str] = {}
if size:
properties["s"] = str(size)
if default:
properties["d"] = default
if properties:
gravatar_url += "?" + urlencode(properties)
return gravatar_url
LETTER_AVATAR_COLORS = [
"#4674ca", # blue
"#315cac", # blue_dark
"#57be8c", # green
"#3fa372", # green_dark
"#f9a66d", # yellow_orange
"#ec5e44", # red
"#e63717", # red_dark
"#f868bc", # pink
"#6c5fc7", # purple
"#4e3fb4", # purple_dark
"#57b1be", # teal
"#847a8c", # gray
]
COLOR_COUNT = len(LETTER_AVATAR_COLORS)
def hash_user_identifier(identifier: str | int) -> int:
identifier = force_str(identifier, errors="replace")
return sum(map(ord, identifier))
def get_letter_avatar_color(identifier: str | int) -> str:
hashed_id = hash_user_identifier(identifier)
return LETTER_AVATAR_COLORS[hashed_id % COLOR_COUNT]
def get_letter_avatar(
display_name: str | None,
identifier: str | int,
size: int | None = None,
use_svg: bool | None = True,
initials: str | None = None,
rounded: bool | None = False,
) -> SafeString:
display_name = (display_name or "").strip() or "?"
names = display_name.split(" ")
initials = initials or "{}{}".format(names[0][0], names[-1][0] if len(names) > 1 else "")
initials = escape(initials.upper())
color = get_letter_avatar_color(identifier)
if use_svg:
size_attrs = f'height="{size}" width="{size}"' if size else ""
return format_html(
'<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" {size_attrs}>'
'<rect x="0" y="0" width="120" height="120" rx="15" ry="15" fill={color}></rect>'
'<text x="50%" y="50%" font-size="65" dominant-baseline="central" text-anchor="middle" fill="#FFFFFF">'
"{initials}"
"</text>"
"</svg>",
color=color,
initials=initials,
size_attrs=size_attrs,
)
else:
size_attrs = f"height:{size}px;width:{size}px;" if size else ""
font_size = "font-size:%spx;" % (size / 2) if size else ""
line_height = "line-height:%spx;" % size if size else ""
span_class = " rounded" if rounded else ""
return format_html(
'<span class="html-avatar{span_class}" '
'style="background-color:{color};{size_attrs}{font_size}{line_height}">'
"{initials}</span>",
color=color,
initials=initials,
size_attrs=size_attrs,
font_size=font_size,
line_height=line_height,
span_class=span_class,
)
def get_email_avatar(
display_name: str | None,
identifier: str,
size: int | None = None,
try_gravatar: bool | None = True,
) -> SafeString:
if try_gravatar:
try:
validate_email(identifier)
except ValidationError:
pass
else:
try:
resp = safe_urlopen(get_gravatar_url(identifier, default=404))
except Exception:
pass
else:
if resp.status_code == 200:
# default to mm if including in emails
gravatar_url = get_gravatar_url(identifier, size=size)
return format_html('<img class="avatar" src="{}">', gravatar_url)
return get_letter_avatar(display_name, identifier, size, use_svg=False)
def get_platform_avatar(
display_name: str | None,
size: int | None = None,
) -> SafeString:
# TODO: @taylangocmen add platformicons from package when available
return format_html(
'<img class="avatar" src="https://raw.githubusercontent.com/getsentry/platformicons/master/svg/{display_name}.svg" height={size}>',
display_name=display_name,
size=size,
)
def is_black_alpha_only(data: IO[bytes]) -> bool:
"""Check if an image has only black pixels (with alpha)"""
result = False
with Image.open(data) as image:
if image.mode == "RGBA":
result = not any(p[:3] != (0, 0, 0) for p in list(image.getdata()))
data.seek(0)
return result