preface
We are about to bid farewell to 2019 and step into a brand new 2020. At the end of the year, each platform collates data and produces an “annual report” of its own. And for techies, if you’re an open source enthusiast, GitHub’s annual report is your tech summary for 2019.
Ruan Yifeng once mentioned the “power of data” in the Weekly science and Technology Enthusiasts:
GitHub’s personal page has a calendar bar that turns green whenever a code is submitted that day. If you code every day of the year, the calendar will be all green, otherwise it will have little white squares. The “coded calendar” is available to all. A lot of people try to submit code every day just to keep the little green squares going. As time went on, I really did many more projects.
Therefore, this annual report I want to focus on this “coding calendar”, put your “coding calendar” together on a picture to show others.
GitHub GraphQL API V4 will be used to obtain relevant user data.
Key technologies covered in this annual report:
- GraphQL
- Python
- For requests.
- PIL: Image/ImageDraw ImageFont (Image processing)
- Werobot (Access wechat official account)
Needs to establish
Before you start Coding, you need to sort out your requirements. The entire process of generating the report is roughly as follows:
So, the things that need to be done include:
- The GitHub GraphQL API V4 was called and the required data was obtained
- Statistical collation of data
- Design an annual report
- Combine the collated data to generate a report and return the final report to the user
- Access the wechat public platform and go through the whole process
Data acquisition
What is a GraphQL?
GitHub GraphQL API V4 to get data, so let’s talk about GraphQL first.
The official definition of GraphQL is:
A query language for apis that is a server-side runtime that executes queries using a type based system (defined by your data).
GitHub REST API V3 and GitHub GraphQL API V4 are used to retrieve data from the GitHub REST API V3.
Take obtaining user data as an example. Related interface documents:
- REST API v3: Users:developer.github.com/v3/users/
- GraphQL API v4: User:developer.github.com/v4/object/u…
For RESTful styles, the natural thing is to make a GET request. Since we want to get data for a specified user, we need to specify :username:
GET /users/:username
Copy the code
GitHub will return the following data after a successful request:
{
"login": "octocat"."id": 1."node_id": "MDQ6VXNlcjE="."avatar_url": "https://github.com/images/error/octocat_happy.gif"."gravatar_id": ""."url": "https://api.github.com/users/octocat"."html_url": "https://github.com/octocat"."followers_url": "https://api.github.com/users/octocat/followers"."following_url": "https://api.github.com/users/octocat/following{/other_user}"."gists_url": "https://api.github.com/users/octocat/gists{/gist_id}"."starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}"."subscriptions_url": "https://api.github.com/users/octocat/subscriptions"."organizations_url": "https://api.github.com/users/octocat/orgs"."repos_url": "https://api.github.com/users/octocat/repos"."events_url": "https://api.github.com/users/octocat/events{/privacy}"."received_events_url": "https://api.github.com/users/octocat/received_events"."type": "User"."site_admin": false."name": "monalisa octocat"."company": "GitHub"."blog": "https://github.com/blog"."location": "San Francisco"."email": "[email protected]"."hireable": false."bio": "There once was..."."public_repos": 2."public_gists": 1."followers": 20."following": 0."created_at": "2008-01-14T04:33:35Z"."updated_at": "2008-01-14T04:33:35Z"
}
Copy the code
But sometimes we don’t need that much data, we might just want to get the user’s profile picture address. With a RESTful interface, we can’t just get a single piece of data, but with the GraphQL interface, we can make a request like this:
{
user(login: "username") {
avatarUrl
}
}
Copy the code
In this way, the server will return the corresponding field according to the format of the data we requested, that is, only the avatarUrl data under user:
{
"data": {"user": {"avatarUrl":"url"}}}Copy the code
In RESTful, we’re forced to accept data that the server has already assembled, but GraphQL gives us more freedom to just take what we need.
In addition, RESTful interfaces are divided by resources, and data is relatively discrete. If you want to request different resources, you need to make multiple requests. GraphQL’s data is more holistic, with resources related to each other in the form of graphs (from which the Graph gets its name), and multiple resources can be retrieved in a single request.
Construct the GraphQL request
The data I want to obtain mainly include:
- The user name
- Daily user contributions in 2019
- Number of Followers
According to the interface documentation User and ContributionsCollection, these data are in User, corresponding to the following fields:
- User name:
name
- Number of Followers:
followers.totalCount
- Coded calendar:
contributionsCollection.contributionCalendar
- Total contribution:
totalContributions
- Weekly contribution:
weeks
- Daily contribution:
contributionDays
- Calendar color:
color
- Contribution of the day:
contributionCount
- Date:
date
- Calendar color:
- Daily contribution:
- Total contribution:
Therefore, we can construct the following query:
query = """ { user(login: "%s") { followers { totalCount } name contributionsCollection( from: "%s", to: "%s" ) { contributionCalendar { totalContributions weeks { contributionDays { color contributionCount date } } } } } } "" "% (github_id, begin, end)
Copy the code
With Query constructed, we use Requests to initiate a request:
import requests
access_token = "xxx"
Request headers with access_token
headers = {"Authorization": "bearer %s" % access_token}
Make a request
response = requests.post(
"https://api.github.com/graphql",
headers=headers,
json={'query': query}
)
Copy the code
If the request is successful, GitHub will return JSON data in the following format:
{
"data": {"user": {"name":"The river does not know"."followers": {"totalCount":71
},
"contributionsCollection": {"contributionCalendar": {"totalContributions":2234."weeks":[
{
"contributionDays":[
{
"color":"#c6e48b"."contributionCount":30."date":"2019-01-01"}]}]}}}}}Copy the code
Data statistics
I mainly made some simple statistics for Weeks. Mainly include:
- Number of days with code submission (
contributionCount > 0
) - Maximum number of consecutive days to submit code
- The date on which the most contributions were made
These data can be obtained by a traversal of weeks, in this do not repeat.
Design report
As a backend developer, I really don’t have much design talent.
The report is broadly divided into three areas:
- The head Title
- “Coded Calendar” under Title
- The middle section shows some analysis data
- The bottom claims sovereignty
I have changed many versions repeatedly and asked many friends for their opinions, but the final result is still not very good…
Data splicing
After the design of the report is complete, the final data to be presented can be spliced into the report.
Draw “Coded Calendar”
In the process of walking through the Weeks statistics, you can incidentally complete the “coding calendar” drawing.
Each day in the “coded calendar” is a small square, the color of which we have retrieved from the color field of the interface’s return data. I chose to use line() to draw a line with the color of the square and make the width of the line bold to get the effect of the square.
from PIL import Image, ImageDraw
# Open image
f = open(self.IMAGE_FILE_PATH, 'rb')
image = Image.open(f)
Create a draw instance
drawImage = ImageDraw.Draw(image)
# iterate over the weekly data
for week in weeks:
Walk through the daily data
for day in week['contributionDays'] :# Take out the color of the day
color = day['color']
# draw a line
drawImage.line([(x_point, y_point), (x_point + square_width, y_point)], fill=color, width=square_width)
# Change the y-coordinate of the next square
y_point += move_width
# Change the x-coordinate of the next square
x_point += move_width
# The next week, the y coordinate will be restored to its original position
y_point = y_begin
Copy the code
Paste the text
The rest of the report is mostly text content, setting up fonts, colors, etc., and using text() to paste text in the specified location.
from PIL import ImageFont
font_size = 60
Set the font size
font = ImageFont.truetype("./font/fzlt.ttf", font_size)
font_color = "#F7FFF7"
# set coordinates
x, y = 0
# Write text on the picture
draImage.text((x, y), "Text to display", fill=font_color, font=font)
Copy the code
Access public Account
The public account directly uses the development framework WeRoBot.
Setting: When a user sends the message “2019 $github_id”, the annual report will be generated.
import werobot
robot = werobot.WeRoBot(token='token')
# reply contains information for the specified text
@robot.filter(re.compile("2019(\s)+(.*)?"))
def annual_report(message, session, match):
if match:
# do something...
Copy the code
After generating the annual report, we used the new temporary material interface of wechat to upload the report picture and obtained the number media_id of the temporary material:
from werobot.client import Client
config = {
"APP_ID": "app_id"."APP_SECRET": "app_secret"
}
client = Client(config)
# Upload temporary material
response = client.upload_media('image', image) # image is the generated report image
Get temporary footage ID
media_id = response['media_id']
Copy the code
Then, we return this image information to the user:
from werobot.replies import ImageReply
# Image data to return
reply = ImageReply(message=message, media_id=media_id)
return reply
Copy the code
The results show
When the user sends 2019+ space +github_id from the public account, the report corresponding to github_id will be returned. The resulting report is as follows:
GitHub repository: github.com/JalanJiang/…
Access to the server for spicy chicken configuration, but also the big guy mercy.
conclusion
The whole process involves the call of wechat official account and GitHub interface, and the user needs to wait several seconds from input to data return. To avoid the embarrassment of time-outs, only a brief analysis of user submission records has been done here.
In the process of completing this project, I wanted to give up several times because the report I designed was too ugly. Thanks to several friends who encouraged me and gave me suggestions for revision, I persevered.
See you in 2019 and hope to try more fun things in 2020. 🙂