├── setup.cfg
├── MANIFEST.in
├── github_email_explorer
├── __init__.py
├── api_url.py
├── cli_explore.py
├── cli_sendgrid.py
├── email_handler.py
└── github_email.py
├── .gitignore
├── examples
├── oauth_github.png
├── marketing_email.png
├── marketing_email.txt
└── get_email.py
├── requirements.txt
├── .travis.yml
├── .codeclimate.yml
├── setup.py
└── README.md
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file=README.md
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include requirements.txt
3 |
--------------------------------------------------------------------------------
/github_email_explorer/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.2.8'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | venv
3 | *.pyc
4 | .idea
5 | .ropeproject
6 | *.log
7 | *.egg-info
8 | dist
9 |
--------------------------------------------------------------------------------
/examples/oauth_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuecen/github-email-explorer/HEAD/examples/oauth_github.png
--------------------------------------------------------------------------------
/examples/marketing_email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuecen/github-email-explorer/HEAD/examples/marketing_email.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Jinja2==2.10.1
2 | MarkupSafe==0.23
3 | python-http-client==2.1.1
4 | requests==2.20.0
5 | sendgrid==3.1.10
6 | tabulate==0.7.5
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | # command to install dependencies
5 | install: "pip install -r requirements.txt"
6 | # command to run tests
7 | script: nosetests
8 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | radon:
3 | enabled: true
4 | config:
5 | python_version: 2
6 |
7 | # required for grades to appear in web UI
8 | ratings:
9 | paths:
10 | - "**.py"
11 |
--------------------------------------------------------------------------------
/examples/marketing_email.txt:
--------------------------------------------------------------------------------
1 | subject: Thanks for trying {{repository}}
2 | from: test@example.com
3 | user:
4 | repository: yuecen/github-email-explorer
5 | repository_owner: yuecen
6 | repository_name: github-email-explorer
7 | site: GitHub
8 |
9 |
Hi {{user.name}} ({{user.g_id}}),
10 | Thank you for trying {{repository_owner}}/{{repository_name}}!
11 |
12 | ...
13 |
14 | I look forward to seeing you on GitHub :)
15 | yuecen
16 |
--------------------------------------------------------------------------------
/examples/get_email.py:
--------------------------------------------------------------------------------
1 | from github_email_explorer import github_email
2 |
3 | ges = github_email.collect_email_info('yuecen', 'github-email-explorer', ['star', 'watch'])
4 |
5 | for ge in ges:
6 | print ge.g_id, "->", ge.name, ",", ge.email
7 |
8 | # With Authentication
9 | # github_api_auth = ('', '')
10 | # ges = github_email.collect_email_info('yuecen', 'github-email-explorer', ['star', 'watch'],
11 | # github_api_auth=github_api_auth)
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import re
3 | from setuptools import setup, find_packages
4 |
5 | pkg_file = open("github_email_explorer/__init__.py").read()
6 | metadata = dict(re.findall("__([a-z]+)__\s*=\s*'([^']+)'", pkg_file))
7 | description = open('README.md').read()
8 | requirements = [i.strip() for i in open("requirements.txt").readlines()]
9 |
10 | setup(
11 | name='github-email-explorer',
12 | description='A tool to get email addresses by action types such as starred, watching or fork on GitHub repositories; Send email content to those addresses with a template.',
13 | version=metadata['version'],
14 |
15 | # Author details
16 | author='yuecen',
17 | author_email='yuecendev+pypi@gmail.com',
18 | url='https://github.com/yuecen/github-email-explorer',
19 | long_description=description,
20 | license='MIT',
21 | classifiers=[
22 | 'Intended Audience :: Developers',
23 | 'Programming Language :: Python',
24 | 'Topic :: Communications :: Email',
25 | 'Topic :: Software Development :: Libraries :: Python Modules',
26 | ],
27 | keywords='github, email, sendgrid, marketing',
28 | install_requires=requirements,
29 | packages=find_packages(),
30 | entry_points={
31 | 'console_scripts': [
32 | 'ge-explore = github_email_explorer.cli_explore:get_github_email_by_repo',
33 | 'ge-sendgrid = github_email_explorer.cli_sendgrid:send_email_by_sendgrid'
34 | ]
35 | }
36 | )
--------------------------------------------------------------------------------
/github_email_explorer/api_url.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from urllib import urlencode
3 | from urlparse import parse_qs, urlsplit, urlunsplit
4 |
5 |
6 | class GitHubEndPoint(object):
7 | api_url = 'https://api.github.com/'
8 |
9 | @staticmethod
10 | def user_profile(user_id):
11 | return '{}users/{}'.format(GitHubEndPoint.api_url, user_id)
12 |
13 | @staticmethod
14 | def user_events(user_id):
15 | return '{}users/{}/events/public'.format(GitHubEndPoint.api_url, user_id)
16 |
17 | @staticmethod
18 | def repository(user_id, repo):
19 | return '{}repos/{}/{}'.format(GitHubEndPoint.api_url, user_id, repo)
20 |
21 | @staticmethod
22 | def stargazers(user_id, repo):
23 | return '{}repos/{}/{}/stargazers'.format(GitHubEndPoint.api_url, user_id, repo)
24 |
25 | @staticmethod
26 | def forks(user_id, repo):
27 | return '{}repos/{}/{}/forks'.format(GitHubEndPoint.api_url, user_id, repo)
28 |
29 | @staticmethod
30 | def watchers(user_id, repo):
31 | return '{}repos/{}/{}/subscribers'.format(GitHubEndPoint.api_url, user_id, repo)
32 |
33 | @staticmethod
34 | def rate_limit():
35 | return '{}rate_limit'.format(GitHubEndPoint.api_url)
36 |
37 | @staticmethod
38 | def add_auth_info(url, github_api_auth):
39 | if github_api_auth is None:
40 | github_api_auth = ('', '')
41 | url = set_url_parameter(url, 'client_id', github_api_auth[0])
42 | url = set_url_parameter(url, 'client_secret', github_api_auth[1])
43 | return url
44 |
45 | @staticmethod
46 | def pagination(url, page=1, per_page=100):
47 | url = set_url_parameter(url, 'page', page)
48 | url = set_url_parameter(url, 'per_page', per_page)
49 | return url
50 |
51 |
52 | def set_url_parameter(url, param_name, param_value):
53 | """Set or replace a query parameter and return the modified URL.
54 |
55 | >>> set_url_parameter('http://example.com?foo=bar&biz=baz', 'foo', 'stuff')
56 | 'http://example.com?foo=stuff&biz=baz'
57 |
58 | """
59 | scheme, netloc, path, query_string, fragment = urlsplit(url)
60 | query_params = parse_qs(query_string)
61 |
62 | query_params[param_name] = [param_value]
63 | new_query_string = urlencode(query_params, doseq=True)
64 |
65 | return urlunsplit((scheme, netloc, path, new_query_string, fragment))
--------------------------------------------------------------------------------
/github_email_explorer/cli_explore.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from collections import namedtuple
3 | from datetime import datetime
4 | from tabulate import tabulate
5 | import argparse
6 | import re
7 |
8 | import github_email
9 |
10 |
11 | def record_args(name, d):
12 | return namedtuple(name, d.keys())(**d)
13 |
14 |
15 | class ExploreCliArgs(object):
16 | def __init__(self):
17 | p = argparse.ArgumentParser(prog='ge-explore')
18 | p.add_argument('--repo', help='Repo on Github, type "/"')
19 | p.add_argument('--action_type', default=['star'], nargs='+', help='"star", "fork" and "watch" are the only three options now')
20 | p.add_argument('--client_id', help='Github OAuth client ID')
21 | p.add_argument('--client_secret', help='Github OAuth client secret')
22 | p.add_argument('--status', action='store_true', help='Github API status')
23 |
24 | args = p.parse_args()
25 |
26 | self.repo = args.repo
27 | if self.repo:
28 | tmp = re.split('/', self.repo)
29 | assert len(tmp) == 2, "repo format is not correct"
30 |
31 | self.repo = record_args('repo', {'owner': tmp[0], 'name': tmp[1]})
32 |
33 | self.action_type = args.action_type
34 | self.client_id = args.client_id if args.client_id else ''
35 | self.client_secret = args.client_secret if args.client_secret else ''
36 | self.status = args.status
37 |
38 |
39 | def get_github_email_by_repo():
40 | """ Get user email by repos
41 | """
42 | explore_cli_args = ExploreCliArgs()
43 |
44 | github_api_auth = (explore_cli_args.client_id, explore_cli_args.client_secret)
45 |
46 | if explore_cli_args.status:
47 | # call api status
48 | status = github_email.api_status(github_api_auth)
49 | table = [["Core", status.core_limit, status.core_remaining, datetime.utcfromtimestamp(status.core_reset_time).strftime('%Y-%m-%dT%H:%M:%SZ')],
50 | ["Search", status.search_limit, status.search_remaining, datetime.utcfromtimestamp(status.search_reset_time).strftime('%Y-%m-%dT%H:%M:%SZ')]]
51 | print "== GitHub API Status =="
52 | print tabulate(table, headers=['Resource Type', 'Limit', 'Remaining', 'Reset Time'])
53 | return
54 |
55 | # handle action_type
56 | ges = github_email.collect_email_info(explore_cli_args.repo.owner, explore_cli_args.repo.name, explore_cli_args.action_type, github_api_auth)
57 | print 'Total: {}/{}'.format(len([ge for ge in ges if ge.email]), len(ges))
58 | print github_email.format_email(ges)
59 |
60 |
61 | if __name__ == '__main__':
62 | get_github_email_by_repo()
63 |
--------------------------------------------------------------------------------
/github_email_explorer/cli_sendgrid.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import argparse
4 |
5 | import github_email
6 | from email_handler import get_email_template
7 | from email_handler import parse_into_github_user_emails
8 | from email_handler import send_sendgrid_by_ges
9 |
10 |
11 | class SendGridCliArgs(object):
12 | def __init__(self):
13 | p = argparse.ArgumentParser(prog='ge-sendgrid')
14 | p.add_argument('--api_key', help='Your KEY of SendGrid API')
15 | p.add_argument('--template_path', help='Your email template')
16 | p.add_argument('--action_type', default=['star'], nargs='+', help='"star", "fork" and "watch" are the only three options now')
17 | p.add_argument('--client_id', help='Github OAuth client ID')
18 | p.add_argument('--client_secret', help='Github OAuth client secret')
19 |
20 | args = p.parse_args()
21 |
22 | self.api_key = args.api_key
23 | self.action_type = args.action_type
24 | self.template_path = args.template_path
25 | self.client_id = args.client_id if args.client_id else ''
26 | self.client_secret = args.client_secret if args.client_secret else ''
27 |
28 |
29 | def send_email_by_sendgrid():
30 | """
31 | Send email via SendGrid
32 | """
33 | sendgrid_cli_args = SendGridCliArgs()
34 |
35 | github_email_template = get_email_template(sendgrid_cli_args.template_path)
36 | metadata = github_email_template.metadata
37 | # List first
38 | if metadata['user']:
39 | print "Send email by list..."
40 | ges = parse_into_github_user_emails(metadata['user'])
41 | print 'Total email addresses from list: {}'.format(len(ges))
42 | send_sendgrid_by_ges(github_user_emails=ges,
43 | sendgrid_api_key=sendgrid_cli_args.api_key,
44 | github_email_template=github_email_template)
45 |
46 | else:
47 | print "Send email by exploring..."
48 | # explore users email by action types
49 | github_api_auth = (sendgrid_cli_args.client_id, sendgrid_cli_args.client_secret)
50 | ges = github_email.collect_email_info(metadata['repository_owner'], metadata['repository_name'], sendgrid_cli_args.action_type, github_api_auth)
51 | print 'Total: {}/{}'.format(len([ge for ge in ges if ge.email]), len(ges))
52 |
53 | # send email by py-sendgrid
54 | send_sendgrid_by_ges(github_user_emails=ges,
55 | sendgrid_api_key=sendgrid_cli_args.api_key,
56 | github_email_template=github_email_template)
57 |
58 |
59 | if __name__ == '__main__':
60 | send_email_by_sendgrid()
61 |
--------------------------------------------------------------------------------
/github_email_explorer/email_handler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from github_email import GithubUserEmail
3 |
4 | from jinja2 import FileSystemLoader
5 | from jinja2 import Environment
6 |
7 | from sendgrid import SendGridAPIClient
8 | from sendgrid.helpers.mail import Email, Content, Mail
9 |
10 | import codecs
11 | import re
12 | import sys
13 |
14 |
15 | template_loader = FileSystemLoader(searchpath="/")
16 | template_env = Environment(loader=template_loader)
17 |
18 | ESSENTIAL_FIELDS = ['subject', 'from', 'from_name', 'user',
19 | 'repository', 'repository_owner', 'repository_name']
20 |
21 |
22 | def parse_email(address):
23 | r = re.match('(?P.*)\s*\((?P.*)\)\s*<(?P.*?)>', address)
24 | if r:
25 | e = r.groupdict()
26 | name, g_id, email = e['name'].strip(), e['g_id'].strip(), e['email'].strip()
27 | else:
28 | name, g_id, email = '', '', address.strip()
29 |
30 | return name, g_id, email
31 |
32 | def parse_into_github_user_emails(email_list):
33 | email_list = re.split(';', email_list)
34 | github_user_emails = [GithubUserEmail(parse_email(address)) for address in email_list if len(address.strip()) > 0]
35 | return github_user_emails
36 |
37 |
38 | def send_sendgrid_by_ges(github_user_emails=None, sendgrid_api_key=None, github_email_template=None):
39 | github_user_emails = [ge for ge in github_user_emails if ge.email]
40 |
41 | send_sendgrid(github_user_emails=github_user_emails, sendgrid_api_key=sendgrid_api_key,
42 | github_email_template=github_email_template)
43 |
44 |
45 | def send_sendgrid(sendgrid_api_key=None, github_email_template=None, github_user_emails=None):
46 |
47 | assert sendgrid_api_key, "SendGrid API key is required"
48 |
49 | sg = SendGridAPIClient(apikey=sendgrid_api_key)
50 |
51 | metadata = github_email_template.metadata
52 |
53 | from_email = Email(metadata['from'])
54 | from_email.name = metadata['from_name']
55 | for ge in github_user_emails:
56 |
57 | # Add github_user into metadata
58 | metadata['user'] = ge
59 |
60 | # Render content with metadata
61 | content = Content("text/html", github_email_template.render_content(metadata))
62 |
63 | # Render subject with metadata
64 | subject = template_env.from_string(metadata['subject']).render(metadata)
65 |
66 | to_email = Email(ge.email)
67 | mail = Mail(from_email, subject, to_email, content)
68 | _body = mail.get()
69 |
70 | # Add custom args for log fields
71 | _custon = {}
72 | for key, value in metadata.iteritems():
73 | if key not in ESSENTIAL_FIELDS:
74 | _custon[key] = value
75 | _custon['repository'] = metadata['repository']
76 | _body['custom_args'] = _custon
77 |
78 | response = sg.client.mail.send.post(request_body=_body)
79 |
80 | if response.status_code > 300:
81 | # More than 300 means something wrong, do nothing.
82 | sys.exit()
83 |
84 |
85 | def get_email_template(path):
86 | github_email_template = GitHubEmailTemplate()
87 | github_email_template.set_material(path)
88 | return github_email_template
89 |
90 |
91 | class BaseReader(object):
92 | def __init__(self, *args, **kwargs):
93 | pass
94 |
95 | def read(self, source_path):
96 | """Return metadata and template"""
97 | material = material_open(source_path)
98 | res = re.match(r'(?P[\s\S]*?)\n\n(?P[\s\S]*)', material).groupdict()
99 | return self._parse_metadata(res['metadata']), res['template']
100 |
101 | def _parse_metadata(self, meta):
102 | # add essential fields
103 | format_meta = {ess_field: '' for ess_field in ESSENTIAL_FIELDS}
104 | res = re.findall(r'([\s\S]*?):([\s\S]*?)(\n|$)', meta)
105 | for r in res:
106 | format_meta[r[0].strip()] = r[1].strip()
107 |
108 | return format_meta
109 |
110 |
111 | class GitHubEmailTemplate(BaseReader):
112 | def __init__(self, *args, **kwargs):
113 | super(GitHubEmailTemplate, self).__init__(*args, **kwargs)
114 | self._email_template = None
115 | self.metadata = {}
116 |
117 | def set_material(self, source_path):
118 | self.metadata, template = self.read(source_path)
119 | self._email_template = template_env.from_string(template)
120 |
121 | def render_content(self, meta):
122 | """Render email content with a template."""
123 | if meta:
124 | return self._email_template.render(meta)
125 | else:
126 | return self._email_template.render(self.metadata)
127 |
128 |
129 | def material_open(filename, mode='rb', strip_crs=(sys.platform == 'win32')):
130 | with codecs.open(filename, mode, encoding='utf-8') as infile:
131 | content = infile.read()
132 | if content[0] == codecs.BOM_UTF8.decode('utf8'):
133 | content = content[1:]
134 | if strip_crs:
135 | content = content.replace('\r\n', '\n')
136 | return content
137 |
138 |
139 | if __name__ == '__main__':
140 | # send_sendgrid_by_email_list(' ; Peter James ;')
141 |
142 | gt = GitHubEmailTemplate()
143 | gt.set_material('../examples/marketing_email.txt')
144 | print gt.render_content()
145 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## github-email-explorer
2 |
3 | [](https://travis-ci.org/yuecen/github-email-explorer)
4 | [](https://codeclimate.com/github/yuecen/github-email-explorer)
5 |
6 | For people who want to create an email marketing plan for particular group on
7 | GitHub, github-email-explorer can collect addresses from a repository you want,
8 | and then send email content to those email addresses.
9 |
10 | ### Installation
11 |
12 | ```bash
13 | pip install github-email-explorer
14 | ```
15 |
16 | There are two commends can be used in github-email-explorer,
17 |
18 | * ```ge-explore```: Get email address list from stargazers, forks or watchers on a repository
19 | * ```ge-sendgrid```: Send email by list or repository name with SendGrid API
20 |
21 | SendGrid is only one email provider at current progress.
22 |
23 | ### Example of Getting Email Addresses from Stargazers, Forks or Watchers
24 |
25 | #### A. Using Command
26 |
27 | ```bash
28 | $ ge-explore --repo yuecen/github-email-explorer --action_type star fork watch
29 |
30 | John (john2) ; Peter James (pjames) ;
31 | ```
32 |
33 | You can get user email by ```ge-explore``` with ```/```. The email
34 | addresses are responded in a formatted string. You can copy contact list to any
35 | email service you have, then send your email with those contact address.
36 |
37 | (If you encounter the situation of limitation from GitHub server during running
38 | the command, please add ```--client_id --client_secret ```
39 | with the command above. Get *Client ID* and *Client Secret* by [OAuth applications].)
40 |
41 | #### B. Using Python Script
42 |
43 | ```python
44 | from github_email_explorer import github_email
45 |
46 | ges = github_email.collect_email_info('yuecen', 'github-email-explorer', ['star', 'watch'])
47 |
48 | for ge in ges:
49 | print ge.g_id, "->", ge.name, ",", ge.email
50 |
51 | # With Authentication
52 | # github_api_auth = ('', '')
53 | # ges = github_email.collect_email_info('yuecen', 'github-email-explorer', ['star', 'watch'],
54 | # github_api_auth=github_api_auth)
55 | ```
56 |
57 | ```bash
58 | $ python examples/get_email.py
59 |
60 | 0john123 -> P.J. John, john@example.org
61 | pjames56 -> Peter James, james@example.org
62 | ```
63 |
64 | You can find get_email.py in *examples* folder.
65 |
66 | ### How to Send a Email to GitHub Users from a Particular Repository?
67 |
68 | #### 1. Write Email Content with Template Format
69 |
70 | The [Jinja2] is used to render email content in github-email-explorer, basic
71 | [expressions] make email content more flexible for personal information.
72 |
73 | Here is an example to use following syntax, the file saved to ```examples/marketing_email.txt```
74 |
75 | ```
76 | subject: Thanks for using {{repository}}
77 | from: test@example.com
78 | user:
79 | repository: yuecen/github-email-explorer
80 | repository_owner: yuecen
81 | repository_name: github-email-explorer
82 | site: GitHub
83 |
84 | Hi {{ user.name }} ({{ user.g_id }}),
85 | Thank you for trying {{ repository_owner }}/{{ repository_name }}!
86 |
87 | ...
88 |
89 | I look forward to seeing you on GitHub :)
90 | yuecen
91 | ```
92 |
93 | | Metadata Field | Description |
94 | | --------------- |:------------- |
95 | | subject | email subject |
96 | | from | sender address|
97 | | from_name | sender name |
98 | | user | you can put an email list with a well format for parse user's ```name``` and ```g_id```. For example, ```John (john2) ; Peter James (pjames) ```. If you don't put an email list, the repository field will be used for running ge-explore to get email list. |
99 | | repository | full repository name on GitHub|
100 | | repository_owner| repository owner |
101 | | repository_name | repository name |
102 |
103 | ```site``` is not a essential field, it will be in SendGrid custom_args field for log
104 |
105 | You can use syntax ```{{ ... }}``` to substitute metadata field in runtime stage for personal information.
106 |
107 | #### 2. Send Email
108 |
109 | In order to send email to many users flexibly, we combine the email list from
110 | result of ge-explore and SendGrid to approach it.
111 |
112 | ```
113 | ge-sendgrid --api_key
114 | --template_path /examples/marketing_email.txt
115 | ```
116 |
117 | The following image is an real example of email format for ge-sendgrid command.
118 |
119 | >
120 |
121 | ### More...
122 |
123 | In order to understand API [rate limit] you are using, the status information
124 | can be found by github-email-explorer command.
125 |
126 | Without authentication
127 |
128 | ```bash
129 | $ ge-explore --status
130 |
131 | Resource Type Limit Remaining Reset Time
132 | --------------- ------- ----------- --------------------
133 | Core 60 60 2016-07-07T04:56:12Z
134 | Search 10 10 2016-07-07T03:57:12Z
135 | ```
136 |
137 | With authentication
138 |
139 | You can request more than 60 using authentication by [OAuth applications]
140 |
141 | ```bash
142 | $ ge-explore --status --client_id --client_secret
143 |
144 | == GitHub API Status ==
145 | Resource Type Limit Remaining Reset Time
146 | --------------- ------- ----------- --------------------
147 | Core 5000 5000 2016-07-06T07:59:47Z
148 | Search 30 30 2016-07-06T07:00:47Z
149 | ```
150 |
151 |
152 | [rate limit]:https://developer.github.com/v3/rate_limit/
153 | [OAuth applications]:https://github.com/settings/developers
154 | [Jinja2]:http://jinja.pocoo.org/
155 | [expressions]:http://jinja.pocoo.org/docs/dev/templates/#expressions
156 |
--------------------------------------------------------------------------------
/github_email_explorer/github_email.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from collections import OrderedDict
4 | import requests
5 | from api_url import GitHubEndPoint as EndPoint
6 | import re
7 |
8 |
9 | class GithubUserEmail(object):
10 | def __init__(self, *args, **kwargs):
11 | self.g_id = None
12 | self.name = kwargs.get('name', None)
13 | self.email = kwargs.get('email', None)
14 | self.from_profile = kwargs.get('from_profile', None)
15 | if len(args) > 0 and (type(args[0]) is tuple):
16 | self.name = args[0][0]
17 | self.g_id = args[0][1]
18 | self.email = args[0][2]
19 | self.from_profile = args[0][3]
20 |
21 |
22 | class GithubAPIStatus(object):
23 | def __init__(self):
24 | self.core_limit = None
25 | self.core_remaining = None
26 | self.core_reset_time = None
27 | self.search_limit = None
28 | self.search_remaining = None
29 | self.search_reset_time = None
30 |
31 |
32 | class GithubRepository(object):
33 | def __init__(self):
34 | self.repo_id = None
35 | self.name = None
36 | self.description = None
37 | self.stargazers_count = 0
38 | self.watchers_count = 0
39 | self.forks_count = 0
40 |
41 |
42 | def select_end_porint_builder(act_type):
43 | return {
44 | 'star': EndPoint.stargazers,
45 | 'fork': EndPoint.forks,
46 | 'watch': EndPoint.watchers,
47 | }[act_type]
48 |
49 |
50 | def select_action_count(github_repo, action_type):
51 | if action_type == 'star':
52 | return github_repo.stargazers_count
53 | if action_type == 'fork':
54 | return github_repo.forks_count
55 | if action_type == 'watch':
56 | return github_repo.watchers_count
57 |
58 |
59 | def integrate_user_ids(user_id, repo, actions, github_api_auth):
60 | user_ids = []
61 | for action_type in actions:
62 | # get repo
63 | github_repo = repository(user_id, repo, github_api_auth)
64 | # pagination
65 | per_page = 100
66 | total_pages = select_action_count(github_repo, action_type) / per_page
67 | # create url
68 | url = EndPoint.add_auth_info(select_end_porint_builder(action_type)(user_id, repo), github_api_auth)
69 | # get id by rolling pages
70 | user_ids = user_ids + request_user_ids_by_roll_pages(url, total_pages, per_page)
71 |
72 | return OrderedDict.fromkeys(user_ids).keys()
73 |
74 |
75 | def request_user_ids_by_roll_pages(url, total_pages, per_page):
76 | # loop page with url
77 | user_ids = []
78 | for i in range(0, total_pages + 1):
79 | url = EndPoint.pagination(url, page=(i + 1), per_page=per_page)
80 | r = requests.get(url)
81 |
82 | # raise error when found nothing
83 | r.raise_for_status()
84 |
85 | # handling result
86 | user_ids = user_ids + [info['login'] if 'login' in info else info['owner']['login'] for info in r.json()]
87 |
88 | return user_ids
89 |
90 |
91 | def collect_email_info(repo_user_id, repo_name, actions, github_api_auth=None):
92 | # get user ids
93 | user_ids = integrate_user_ids(repo_user_id, repo_name, actions, github_api_auth)
94 | # get and return email info
95 | return users_email_info(user_ids, github_api_auth)
96 |
97 |
98 | def users_email_info(action_user_ids, github_api_auth):
99 | ges = []
100 | for user_id in action_user_ids:
101 | try:
102 | ges.append(request_user_email(user_id, github_api_auth))
103 | except requests.exceptions.HTTPError as e:
104 | print e
105 | # Return email addresses that have received after exception happened
106 | return ges
107 |
108 | return ges
109 |
110 | def get_email_from_events(rsp, name):
111 | """
112 | Parses out the email, if available from a user's public events
113 | """
114 | rsp = rsp.json()
115 | for event in rsp:
116 | payload = event.get('payload')
117 | if payload is not None:
118 | commits = payload.get('commits')
119 | if commits is not None:
120 | for commit in commits:
121 | author = commit.get('author')
122 | if author['name'] == name:
123 | return author.get('email')
124 |
125 | return None
126 |
127 | def request_user_email(user_id, github_api_auth):
128 | """
129 | Get email from the profile
130 | """
131 |
132 | rsp = requests.get(EndPoint.add_auth_info(EndPoint.user_profile(user_id), github_api_auth))
133 | # raise error when found nothing
134 | rsp.raise_for_status()
135 |
136 | rsp = rsp.json()
137 | ge = GithubUserEmail()
138 | ge.g_id = rsp['login']
139 | ge.name = rsp['name'].strip() if rsp['name'] else rsp['login']
140 | ge.email = rsp['email']
141 | ge.from_profile = True
142 |
143 | # Get user email from events
144 | if ge.email is None:
145 | rsp = requests.get(EndPoint.add_auth_info(EndPoint.user_events(user_id), github_api_auth))
146 | # raise error when found nothing
147 | rsp.raise_for_status()
148 |
149 | email = get_email_from_events(rsp, ge.name)
150 | if email is not None:
151 | ge.email = email
152 | ge.from_profile = False
153 |
154 | # Check if user opted out and respect that
155 | if user_has_opted_out(ge.email):
156 | ge.email = None
157 |
158 | return ge
159 |
160 | def user_has_opted_out(email):
161 | """
162 | Checks if an email address was marked as opt-out
163 | """
164 | if email is not None:
165 | regex = re.compile('\\+[^@]*optout@g(?:oogle)?mail\\.com$', re.IGNORECASE)
166 | return regex.search(email) is not None
167 | else:
168 | return False
169 |
170 |
171 | def format_email(ges):
172 | """
173 | John (john2) ; Peter James (pjames)
174 | """
175 | formatted_email = []
176 | for ge in ges:
177 | if ge.email:
178 | try:
179 | formatted_email.append('{} ({}) <{}> [{}]'.format(ge.name.encode('utf8'), ge.g_id, ge.email, ge.from_profile))
180 | except UnicodeEncodeError:
181 | print ge.g_id, ge.email, ge.from_profile
182 | continue
183 |
184 | formatted_email = '\n'.join(formatted_email)
185 | return formatted_email
186 |
187 |
188 | def api_status(github_api_auth):
189 | rsp = requests.get(EndPoint.add_auth_info(EndPoint.rate_limit(), github_api_auth))
190 | rsp = rsp.json()
191 | status = GithubAPIStatus()
192 | status.core_reset_time = rsp['resources']['core']['reset']
193 | status.core_limit = rsp['resources']['core']['limit']
194 | status.core_remaining = rsp['resources']['core']['remaining']
195 | status.search_reset_time = rsp['resources']['search']['reset']
196 | status.search_limit = rsp['resources']['search']['limit']
197 | status.search_remaining = rsp['resources']['search']['remaining']
198 | return status
199 |
200 |
201 | def repository(user_id, repo, github_api_auth):
202 | rsp = requests.get(EndPoint.add_auth_info(EndPoint.repository(user_id, repo), github_api_auth))
203 | rsp = rsp.json()
204 | repo = GithubRepository()
205 | repo.repo_id = rsp['id']
206 | repo.name = rsp['name']
207 | repo.description = rsp['description']
208 | repo.stargazers_count = rsp['stargazers_count']
209 | repo.watchers_count = rsp['watchers_count']
210 | repo.forks_count = rsp['forks_count']
211 | return repo
212 |
213 |
214 | if __name__ == '__main__':
215 | # print request_user_email('yuecen')
216 | ges = collect_email_info('yuecen', 'github-email-explorer', ['star'])
217 | print 'Total: {}/{}'.format(len([ge for ge in ges if ge.email]), len(ges))
218 | print format_email(ges)
219 |
--------------------------------------------------------------------------------