Рука об руку, чтобы сделать годовой отчет GitHub за 2019 год

Python
Рука об руку, чтобы сделать годовой отчет GitHub за 2019 год

предисловие

Мы собираемся попрощаться с 2019 годом и вступить в совершенно новый 2020 год. В конце года каждая платформа собирает данные и выпускает «годовой отчет» своей платформы. Для технических специалистов, если вы являетесь энтузиастом с открытым исходным кодом, годовой отчет GitHub — это ваше техническое резюме в 2019 году.

Учитель Жуань Ифэн был вЕженедельник технических энтузиастов«Сила данных» упоминается в:

На личной странице GitHub есть раздел календаря, и если в этот день будет отправлен код, маленький квадрат этого дня станет зеленым. Если вы кодируете каждый день в году, календарь будет полностью зеленым, иначе будут маленькие белые квадратики. Каждый может увидеть этот «кодированный календарь». Многие люди пытаются отправлять код каждый день, чтобы зеленые квадраты не прерывались. Со временем я сделал много проектов.

Поэтому в этом годовом отчете я хочу сосредоточиться на этом «календаре кодирования» и собрать ваш «календарь кодирования» в картинку, чтобы показать другим.

Потому что я учился некоторое время назадGraphQL, так будет проходить через интерфейс GitHubGitHub GraphQL API v4для получения соответствующих пользовательских данных.

Основные технологии, рассматриваемые в этом годовом отчете:

  • GraphQL
  • Python
    • requests(Начать запрос)
    • PIL: Image/ImageDraw/ImageFont (обработка изображений)
    • werobot(Доступ к общедоступной учетной записи WeChat)

установление спроса

Прежде чем приступить к кодированию, необходимо разобраться с требованиями. Весь процесс формирования отчета примерно выглядит следующим образом:

项目流程图

Таким образом, необходимо сделать следующее:

  1. Вызовите GitHub GraphQL API v4, чтобы получить необходимые данные.
  2. Статистическая организация данных
  3. Оформить годовой отчет
  4. Генерировать отчеты, объединяющие сопоставленные данные, и возвращать окончательный отчет пользователю.
  5. Получите доступ к общедоступной платформе WeChat и пройдите весь процесс

Сбор данных

Что такое GraphQL?

Поскольку мы хотим получать данные через GitHub GraphQL API v4, давайте сначала поговорим о GraphQL.

Официальное определение GraphQL:

Язык запросов для API, среда выполнения на стороне сервера, которая выполняет запросы с использованием системы на основе типов, определяемой вашими данными.

Это очень абстрактно, возможно, вы правы.RESTfulпривычнее, то возьмемGitHub REST API v3Простое сравнение с тем, как GitHub GraphQL API v4 получает данные, позволяет понять характеристики GraphQL с первого взгляда.

Взяв за пример получение пользовательских данных, соответствующие интерфейсные документы:

Для стиля RESTful естественно инициироватьGETпросить. Так как мы хотим получитьназначенный пользовательdata, поэтому его нужно указать в PATH:username:

GET /users/:username

После успешного выполнения запроса GitHub вернет следующие данные:

{
  "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": "octocat@github.com",
  "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"
}

Но иногда нам не нужно столько данных, мы можем просто захотеть получить адрес аватара пользователя. Под интерфейсом RESTful мы не можем получить только один кусок данных, но для интерфейса GraphQL мы можем инициировать такой запрос:

{
    user(login: "username") {
        avatarUrl
    }
}

Таким образом, сервер будет возвращать нам соответствующие поля согласно формату запрашиваемых нами данных, то есть возвращать толькоuserвнизavatarUrlданные:

{
    "data":{
        "user":{
            "avatarUrl":"url"
        }
    }
}

В RESTful мы вынуждены получать данные, уже собранные на сервере, но GraphQL дает нам больше свободы для получения только того, что нам нужно.

Кроме того, RESTful разделяет интерфейс по ресурсам, а данные относительно дискретны.Если вы хотите запрашивать разные ресурсы, вам нужно инициировать несколько запросов. С другой стороны, данные GraphQL более целостны, а ресурсырисунок(то есть источник имени Graph) связаны друг с другом, и одним запросом можно получить несколько ресурсов.

Создание запроса GraphQL

Данные, которые я хочу получить, в основном:

  1. имя пользователя
  2. Ежедневные взносы пользователей в 2019 году
  3. Подписчики пользователей

Согласно документации интерфейсаUserа такжеContributionsCollectionВидно, что эти данныеuser, соответствующие поля выглядят следующим образом:

  • Никнейм пользователя:name
  • Количество подписчиков:followers.totalCount
  • Календарь кодирования:contributionsCollection.contributionCalendar
    • Общее количество взносов:totalContributions
    • Взносы в неделю:weeks
      • Ежедневный вклад:contributionDays
        • Цвет календаря дня:color
        • Взносы за день:contributionCount
        • Сегодняшняя дата:date

Следовательно, его можно построить следующим образомquery:

query = """
{
    user(login: "%s") {
        followers {
            totalCount
        }
        name
        contributionsCollection(
            from: "%s",
            to: "%s"
        ) {
            contributionCalendar {
                totalContributions
                weeks {
                    contributionDays {
                        color
                        contributionCount
                        date
                    }
                }
            }
        }
    }
} 
"""% (github_id, begin, end)

хорошо построенныйqueryПосле этого используемrequestsОбратиться с просьбой:

import requests


access_token = "xxx"

# 请求 headers 带上 access_token
headers = {"Authorization": "bearer %s" % access_token}

# 发起请求
response = requests.post(
    "https://api.github.com/graphql",
    headers=headers,
    json={'query': query}
)

Если запрос выполнен успешно, GitHub вернет данные JSON в следующем формате:

{
    "data":{
        "user":{
            "name":"江不知",
            "followers":{
                "totalCount":71
            },
            "contributionsCollection":{
                "contributionCalendar":{
                    "totalContributions":2234,
                    "weeks":[
                        {
                            "contributionDays":[
                                {
                                    "color":"#c6e48b",
                                    "contributionCount":30,
                                    "date":"2019-01-01"
                                }
                            ]
                        }
                    ]
                }
            }
        }
    }
}

Статистика

я ориентируюсь в основномweeksПроведите простую статистику. в основном включает:

  • Количество дней с коммитами кода (contributionCount > 0)
  • Максимальное количество дней для последовательной отправки кода
  • Дата с наибольшим количеством сделанных взносов

Эти пары данныхweeksЕго можно получить, выполнив обход, и здесь повторяться не будем.

Отчет о дизайне

Как back-end разработчик, у меня действительно нет особого дизайнерского таланта.

Весь отчет условно разделен на три части:

  1. Название главы
  2. «Закодированный календарь» под заголовком
  3. В средней части показаны некоторые аналитические данные.
  4. Суверенитет на дне

Я менял много версий снова и снова и спрашивал мнение многих друзей, но окончательный результат все еще не очень хорош...

年度报告设计最终版

сшивание данных

После того, как дизайн отчета завершен, окончательные данные для отображения могут быть включены в отчет.

Нарисуйте «календарь кодирования»

пересекаяweeksКстати, в процессе статистических данных рисунок «кодированного календаря» можно доделать.

Каждый день в "закодированном календаре" представляет собой квадратик, цвет квадратика у нас возвращен данные из интерфейсаcolorполученный с поля. я предпочитаю использоватьline()нарисовать линию цветаcolorПрямая линия представляет собой квадрат, поместите прямую линиюwidthЖирный для квадратного эффекта.

from PIL import Image, ImageDraw

# 打开图片
f = open(self.IMAGE_FILE_PATH, 'rb')
image = Image.open(f)
# 创建一个 draw 实例
drawImage = ImageDraw.Draw(image)

# 遍历每周数据
for week in weeks:
    # 遍历每日数据
    for day in week['contributionDays']:
        # 取出当天的颜色
        color = day['color'] 
        # 绘制直线
        drawImage.line([(x_point, y_point), (x_point + square_width, y_point)], fill=color, width=square_width)
        # 改变下一个方格的 y 坐标
        y_point += move_width
    # 改变下一个方格的 x 坐标
    x_point += move_width
    # 下一周开始,y 坐标恢复原处
    y_point = y_begin

вставить текст

Другие части отчета в основном текстового содержания, установить шрифт, цвет и т.д., использоватьtext()Вставьте текст в указанное место.

from PIL import ImageFont

font_size = 60
# 设置字体与字号
font = ImageFont.truetype("./font/fzlt.ttf", font_size)
font_color = "#F7FFF7"

# 设置坐标
x, y = 0

# 在图片写上文字
draImage.text((x, y), "要显示的文字", fill=font_color, font=font)

Доступ к официальному аккаунту

Публичный аккаунт напрямую использует фреймворк разработкиWeRoBot.

Настройка: инициировать создание годового отчета, когда пользователь отправляет информацию как «2019 $github_id».

import werobot

robot = werobot.WeRoBot(token='token')

# 回复包含指定文本的信息
@robot.filter(re.compile("2019(\s)+(.*)?"))
def annual_report(message, session, match):
    if match:
        # do something...

После создания годового отчета мы используем WeChatДобавить временный материалИнтерфейс загружает изображение отчета и получает номер временного материалаmedia_id:

from werobot.client import Client

config = {
    "APP_ID": "app_id",
    "APP_SECRET": "app_secret"
}

client = Client(config)
# 上传临时素材
response = client.upload_media('image', image) # image 为生成的报告图片
# 获取临时素材 ID
media_id = response['media_id']

Затем мы возвращаем эту информацию об изображении пользователю:

from werobot.replies import ImageReply

# 要返回的图片数据
reply = ImageReply(message=message, media_id=media_id)
return reply

Отображение результатов

Когда пользователь отправляет в публичный аккаунт2019+空格+github_id, вернусьgithub_idсоответствующий отчет. Окончательный сгенерированный отчет выглядит следующим образом:

我的 2019 GitHub 年度报告

См. исходный код в репозитории GitHub:GitHub.com/Дж. Алан Цзян/…

Подключенный сервер настроен на острую курицу, и, пожалуйста, проявите милосердие.

Суммировать

Весь процесс включает в себя вызов общедоступной учетной записи WeChat и интерфейса GitHub, и пользователям необходимо подождать несколько секунд от ввода до возврата данных. Чтобы избежать неловкой ситуации с тайм-аутом, здесь проводится только простой анализ записей, отправленных пользователями.

В процессе завершения этого проекта я несколько раз хотел сдаться, потому что отчет, который я разработал, был слишком уродливым.Благодаря нескольким друзьям, которые всегда поддерживали меня и давали мне предложения по исправлению, я выстоял.

Увидимся в 2019 году и надеемся попробовать еще больше интересных вещей в 2020 году. :)