Skip to content

Commit

Permalink
examples : make badge.py work without GH token
Browse files Browse the repository at this point in the history
If the user doesn't supply GH token, try to fetch and parse profile
information from public HTML
  • Loading branch information
rgerganov committed Sep 24, 2023
1 parent f098c58 commit f517bb2
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 27 deletions.
4 changes: 3 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ $ ./demo.py save
# `badge.py`
Creates a badge-like tag for a GitHub user. Some examples are [octocat](https://ggtag.io/?i=%5Cr10%2C25%2C110%2C110%5CI15%2C30%2C100%2C100%2C1%2Chttps%3A%2F%2Favatars.githubusercontent.com%2Fu%2F583231%3Fv%3D4%5Ct140%2C50%2C5%2CThe%20Octocat%5Ct140%2C80%2C2%2Cgit.luolix.top%2Foctocat%5Ca13%2C156%2C16%2Cmap-marker-alt%5Ct33%2C158%2C2%2CSan%20Francisco%5Ca13%2C183%2C16%2Cbuilding%5Ct33%2C185%2C2%2C%40github%5Ca180%2C154%2C16%2Clink%5Ct202%2C158%2C2%2Chttps%3A%2F%2Fgithub.blog%5Ca180%2C185%2C16%2Cenvelope%5Ct202%2C185%2C2%2Coctocat%40git.luolix.top), [antirez](https://ggtag.io/?i=%5Cr10%2C25%2C110%2C110%5CI15%2C30%2C100%2C100%2C1%2Chttps%3A%2F%2Favatars.githubusercontent.com%2Fu%2F65632%3Fv%3D4%5Ct140%2C40%2C5%2CSalvatore%5Ct140%2C70%2C5%2CSanfilippo%5Ct140%2C110%2C2%2Cgit.luolix.top%2Fantirez%5Ca13%2C156%2C16%2Cmap-marker-alt%5Ct33%2C158%2C2%2CCatania%2CSicily%2CItaly%5Ca13%2C183%2C16%2Cbuilding%5Ct33%2C185%2C2%2CRedis%20Labs%5Ca180%2C154%2C16%2Clink%5Ct202%2C158%2C2%2Chttp%3A%2F%2Finvece.org%5Ca180%2C185%2C16%2Cenvelope%5Ct202%2C185%2C2%2Cantirez%40gmail.com), [ggerganov](https://ggtag.io/?i=%5Cr10%2C25%2C110%2C110%5CI15%2C30%2C100%2C100%2C1%2Chttps%3A%2F%2Favatars.githubusercontent.com%2Fu%2F1991296%3Fv%3D4%5Ct140%2C40%2C5%2CGeorgi%5Ct140%2C70%2C5%2CGerganov%5Ct140%2C110%2C2%2Cgit.luolix.top%2Fggerganov%5Ca13%2C156%2C16%2Cmap-marker-alt%5Ct33%2C158%2C2%2CSofia%2C%20Bulgaria%5Ca13%2C183%2C16%2Cbuilding%5Ct33%2C185%2C2%2C%40viewray-inc%20%5Ca180%2C154%2C16%2Clink%5Ct202%2C158%2C2%2Chttps%3A%2F%2Fggerganov.com%5Ca180%2C185%2C16%2Cenvelope%5Ct202%2C185%2C2%2Cggerganov%40gmail.com), [rgerganov](https://ggtag.io/?i=%5Cr10%2C25%2C110%2C110%5CI15%2C30%2C100%2C100%2C1%2Chttps%3A%2F%2Favatars.githubusercontent.com%2Fu%2F271616%3Fv%3D4%5Ct140%2C40%2C5%2CRadoslav%5Ct140%2C70%2C5%2CGerganov%5Ct140%2C110%2C2%2Cgit.luolix.top%2Frgerganov%5Ca13%2C156%2C16%2Cmap-marker-alt%5Ct33%2C158%2C2%2CSofia%2C%20Bulgaria%5Ca13%2C183%2C16%2Cbuilding%5Ct33%2C185%2C2%2C%40vmware%5Ca180%2C154%2C16%2Clink%5Ct202%2C158%2C2%2Chttps%3A%2F%2Fxakcop.com%5Ca180%2C185%2C16%2Cenvelope%5Ct202%2C185%2C2%2Crgerganov%40gmail.com).

You need to create a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) on GitHub and export it as `GITHUB_TOKEN` environment variable.
For best results, we suggest to create a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) on GitHub and export it as `GITHUB_TOKEN` environment variable.
```
$ export GITHUB_TOKEN=<your-github-token>
$ ./badge.py <github-username>
# You can replace the profile picture with QR code, so you can program the tag with sound:
$ ./badge.py --qrcode <github-username>
```
If you don't set `GITHUB_TOKEN`, the script will try to fetch and parse the profile information from GitHub website, which is less reliable.

# `img.py`
Creates a tag with PNG image specified either as local file or URL:
```
Expand Down
127 changes: 101 additions & 26 deletions examples/badge.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,67 @@
import ggtag
import urllib.request
import argparse
import html.parser

def fetch_profile(token, user):
# This is a hacky HTML parser to extract profile information from GitHub
class GHProfileParser(html.parser.HTMLParser):

@staticmethod
def has_itemprop(attrs, value):
for attr in attrs:
if attr[0] == 'itemprop' and attr[1] == value:
return True
return False

@staticmethod
def get_attr(attrs, key):
for attr in attrs:
if attr[0] == key:
return attr[1]
return None

def __init__(self):
super().__init__()
self.profile = {}
self.in_works_for = False
self.in_homeloc = False
self.in_url = False
self.in_name = False
self.in_followers = False

def set_once(self, key, value):
if key not in self.profile:
self.profile[key] = value

def handle_starttag(self, tag, attrs) -> None:
if tag == 'li' and self.has_itemprop(attrs, 'worksFor'):
self.in_works_for = True
if tag == 'li' and self.has_itemprop(attrs, 'homeLocation'):
self.in_homeloc = True
if tag == 'li' and self.has_itemprop(attrs, 'url'):
self.in_url = True
if tag == 'span' and self.has_itemprop(attrs, 'name'):
self.in_name = True
if tag == 'span' and len(attrs) == 1 and attrs[0][0] == 'class' and attrs[0][1] == 'text-bold color-fg-default':
self.in_followers = True
if tag == 'span' and self.in_works_for:
self.set_once('company', self.get_attr(attrs, 'title'))
if tag == 'a' and self.in_url:
self.set_once('blog', self.get_attr(attrs, 'href'))
if tag == 'a' and self.has_itemprop(attrs, 'image'):
self.set_once('avatar_url', self.get_attr(attrs, 'href'))
return super().handle_starttag(tag, attrs)

def handle_data(self, data: str) -> None:
if self.in_name:
self.set_once('name', data.strip())
if self.in_homeloc and data.strip():
self.set_once('location', data.strip())
if self.in_followers:
self.set_once('followers', data.strip())
return super().handle_data(data)

def fetch_api_profile(token, user):
url = "https://api.github.com/users/{}".format(user)
headers = {
'Accept': 'application/vnd.github+json',
Expand All @@ -15,29 +74,42 @@ def fetch_profile(token, user):
}
req = urllib.request.Request(url, None, headers)
response = urllib.request.urlopen(req).read()
return json.loads(response)
profile = json.loads(response)
if profile['followers'] > 1000:
profile['followers'] = '{}k'.format(profile['followers'] // 1000)
else:
profile['followers'] = '{}'.format(profile['followers'])
return profile

def fetch_html_profile(user):
url = "https://github.com/{}".format(user)
response = urllib.request.urlopen(url)
html_profile = response.read().decode('utf-8')
parser = GHProfileParser()
parser.feed(html_profile)
return parser.profile

def tweak_profile(profile):
if 'location' in profile:
profile['location'] = profile['location'].replace(', ', ',')
profile['followers'] = profile['followers'] + ' followers'
return profile

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Creates a GitHub badge')
parser.add_argument('username', type=str, help='GitHub username')
parser.add_argument('-q', '--qrcode', action='store_true', help='Use QR code instead of profile image')
args = parser.parse_args()
token = os.environ['GITHUB_TOKEN']
if not token:
print("Please set GITHUB_TOKEN environment variable")
sys.exit(1)
username = args.username
profile = fetch_profile(token, username)
token = os.environ.get('GITHUB_TOKEN')
if not token:
print('GITHUB_TOKEN environment variable not set, using HTML parser')
profile = fetch_html_profile(username)
else:
profile = fetch_api_profile(token, username)
profile = tweak_profile(profile)
profile_pic = profile['avatar_url']
name = profile['name']
location = profile['location']
company = profile['company']
blog = profile['blog']
email = profile['email']
if location and len(location) > 15:
# try to make it shorter by removing some spaces
location = location.replace(', ', ',')
tag = ggtag.GGTag()
tag.rect(10, 25, 110, 110)
if not args.qrcode:
Expand All @@ -55,16 +127,19 @@ def fetch_profile(token, user):
else:
tag.text(140, 50, 5, name)
tag.text(140, 80, 2, "github.com/{}".format(username))
if location:
tag.icon(13, 156, 16, 'map-marker-alt')
tag.text(33, 158, 2, location)
if company:
tag.icon(13, 183, 16, 'building')
tag.text(33, 185, 2, company)
if blog:
tag.icon(180, 154, 16, 'link')
tag.text(202, 158, 2, blog)
if email:
tag.icon(180, 185, 16, 'envelope')
tag.text(202, 185, 2, email)

placeholder_icons = [(13, 156), (13, 183), (180, 154), (180, 185)]
placeholder_texts = [(33, 158), (33, 185), (202, 158), (202, 185)]
icon_map = {'location': 'map-marker-alt',
'company': 'building',
'blog': 'link',
'email': 'envelope',
'followers': 'user-friends'}
props = ['location', 'company', 'blog', 'followers', 'email']
index = 0
for prop in props:
if profile.get(prop) and index < 4:
tag.icon(*placeholder_icons[index], 16, icon_map[prop])
tag.text(*placeholder_texts[index], 2, profile[prop])
index += 1
tag.browse()

0 comments on commit f517bb2

Please sign in to comment.