File: script/make_content/make_content.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
#   Author  :   XueWeiHan
#   E-mail  :   595666367@qq.com
#   Date    :   16/10/21 下午1:41
#   Desc    :   HelloGitHub项目——生成月刊脚本
"""
该脚本主要用于：生成月刊

《HelloGitHub》月刊每期内容都遵循统一格式，如果需要对通用部分的内容进行修改，需要手动修改每一
期的内容，这是不优雅的。

所以，我打算写个脚本，用于生成月刊，这样如果修改了通用内容部分，就只需要重新生成月刊，而不需要
手动修改已发布的所有期的内容。
"""
from __future__ import print_function
import sys
import os

CONTENT_FLAG = '{{ hello_github_content }}'
NUM_FLAG = '{{ hello_github_num }}'


class InputError(Exception):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return repr(self.message)


def check_path(path):
    """
    检查路径是否存在
    """
    if not os.path.exists(path):
        print('not exist: {path}'.format(path=path))
        return False
    else:
        return True


def read_file(input_path):
    with open(input_path, 'r') as fb:
        return fb.read()


def write_file(output_path, output_data):
    with open(output_path, 'w') as fb:
        fb.write(output_data)


def make_content(num):
    template_path = os.path.join(os.path.abspath(os.curdir), 'template.md')
    output_path = os.path.join(os.path.abspath(os.curdir), num)
    content_path = os.path.join(output_path, 'content'+num+'.md')
    if not (check_path(content_path) and check_path(template_path)):
        # 如果 content 和 template 文件不存在
        return None
    temple_data = read_file(template_path).replace(NUM_FLAG, num)

    content_data = read_file(content_path)

    output_data = temple_data.replace(CONTENT_FLAG, content_data)

    write_file(os.path.join(output_path, 'HelloGitHub{num}.md'.format(num=num)), output_data)
    print('Make 《GitHub月刊{num}》 successful！'.format(num=num))


def make_all_content():
    dir_list = os.listdir(os.path.abspath(os.curdir))
    for fi_dir in dir_list:
        # 忽略‘script’的目录
        if os.path.isdir(fi_dir) and 'script' not in fi_dir:
            make_content(fi_dir)


def main():
    """
    入口方法
    """
    input_list = sys.argv  # 获取输入的参数

    if len(input_list) != 2:
        raise InputError('Input error: Need a param')
    else:
        try:
            input_arg = input_list[1]
        except Exception:
            raise InputError('Input error: Must be number')
    if len(input_arg) == 1:
        make_content('0' + input_arg)
    elif input_arg == 'all':
        make_all_content()
    else:
        make_content(input_arg)

if __name__ == '__main__':
    main()



File: script/github_bot/github_bot.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
#   Author  :   XueWeiHan
#   E-mail  :   595666367@qq.com
#   Date    :   16/8/30 下午10:43
#   Desc    :   Github Bot
import os
import logging
import smtplib
import datetime
from operator import itemgetter
from email.mime.text import MIMEText
from email.header import Header

import requests

logging.basicConfig(
    level=logging.WARNING,
    filename=os.path.join(os.path.dirname(__file__), 'bot_log.txt'),
    filemode='a',
    format='%(name)s %(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s'
)
logger = logging.getLogger('Bot')  # 设置log名称
# github帐号
ACCOUNT = {
    'username': '',
    'password': ''
}

API = {
    'events': 'https://api.github.com/users/{username}/received_events'.format(username=ACCOUNT['username'])
}

# 发送邮件，邮箱的信息
MAIL = {
    'mail': '',  # 发送邮件的邮箱地址
    'username': '',
    'password': '',
    'host': 'smtp.qq.com',
    'port': 465
}

# 接收邮件的邮箱地址
RECEIVERS = []

# 几天前
DAY = 1

# 项目stars临界值
STARS = 100

# qq邮件服务文档：http://service.mail.qq.com/cgi-bin/help?id=28


CONTENT_FORMAT = """
    <table border="2" align="center">
      <tr>
        <th>头像</th>
        <th>用户名</th>
        <th>项目名</th>
        <th>starred 日期</th>
        <th>项目 star 数量</th>
      </tr>
      {project_info_string}
    </table>
"""


def get_data(page=1):
    """
    从目标源获取数据
    https://developer.github.com/v3/activity/events/
    GitHub 规定：默认每页 30 条，最多 300 条目
    """

    args = '?page={page}'.format(page=page)

    response = requests.get(API['events']+args,
                            auth=(ACCOUNT['username'], ACCOUNT['password']))
    status_code = response.status_code
    if status_code == 200:
        resp_json = response.json()
        return resp_json
    else:
        logging.error('请求 event api 失败：', status_code)
        return []


def get_all_data():
    """
    获取全部 300 条的数据
    https://developer.github.com/v3/activity/events/
    GitHub 规定：默认每页 30 条，最多 300 条目
    """
    all_data_list = []
    for i in range(10):
        response_json = get_data(i+1)
        if response_json:
            all_data_list.extend(response_json)
    return all_data_list


def check_condition(data):
    """
    过滤条件
    """
    create_time = datetime.datetime.strptime(
        data['created_at'], "%Y-%m-%dT%H:%M:%SZ") + datetime.timedelta(hours=8)
    date_condition = create_time >= (datetime.datetime.now()
                                     - datetime.timedelta(days=DAY))
    if (data['type'] == 'WatchEvent') and date_condition:
        # 不统计自己项目的star事件
        if data['payload']['action'] == 'started' and \
           ACCOUNT['username'] not in data['repo']['name']:
            data['date_time'] = create_time.strftime("%Y-%m-%d %H:%M:%S")
            return True
    else:
        return False


def analyze(json_data):
    """
    分析获取的数据
    :return 符合过滤条件的数据
    """
    result_data = []
    for fi_data in json_data:
        if check_condition(fi_data):
            result_data.append(fi_data)
    return result_data


def get_stars(data):
    """
    获取stars数量，同时过滤掉stars数量少的项目
    """
    project_info_list = []
    for fi_data in data:
        project_info = dict()
        project_info['user'] = fi_data['actor']['login']
        project_info['user_url'] = 'https://github.com/' + project_info['user']
        project_info['avatar_url'] = fi_data['actor']['avatar_url']
        project_info['repo_name'] = fi_data['repo']['name']
        project_info['repo_url'] = 'https://github.com/' + project_info['repo_name']
        project_info['date_time'] = fi_data['date_time']
        try:
            repo_stars = requests.get(fi_data['repo']['url'], timeout=2).json()
            if repo_stars:
                project_info['repo_stars'] = int(repo_stars['stargazers_count'])
            else:
                project_info['repo_stars'] = -1
        except Exception as e:
            project_info['repo_stars'] = -1
            logger.warning(u'获取：{} 项目星数失败——{}'.format(
                project_info['repo_name'], e))
        finally:
            if project_info['repo_stars'] >= STARS or project_info['repo_stars'] == -1:
                # 过滤掉star数量低于临界值的项目
                project_info_list.append(project_info)
    project_info_list = sorted(project_info_list, key=itemgetter('repo_stars'), reverse=True)
    return project_info_list


def make_content():
    """
    生成发布邮件的内容
    """
    json_data = get_all_data()
    data = analyze(json_data)
    content = []
    project_info_list = get_stars(data)
    for project_info in project_info_list:
        project_info_string = """<tr>
                                <td><img src={avatar_url} width=32px></img></td>
                                <td><a href={user_url}>{user}</a></td>
                                <td><a href={repo_url}>{repo_name}</a></td>
                                <td>{date_time}</td>
                                <td>{repo_stars}</td>
                              </tr>
                           """.format(**project_info)
        content.append(project_info_string)
    return content


def send_email(receivers, email_content):
    """
    发送邮件
    """
    sender = MAIL['mail']  # 发送邮件的邮箱
    receivers = receivers   # 接收邮件的邮箱，可设置多个

    # 三个参数：第一个为文本内容，第二个 html 设置文本格式，第三个 utf-8 设置编码
    message = MIMEText(
        CONTENT_FORMAT.format(project_info_string=''.join(email_content)),
        'html', 'utf-8'
    )
    message['From'] = Header(u'GitHub 机器人', 'utf-8')
    message['To'] = Header(u'削微寒', 'utf-8')

    subject = u'今日 GitHub 热点'  # 设置邮件主题
    message['Subject'] = Header(subject, 'utf-8')
    try:
        smtp_obj = smtplib.SMTP_SSL()  # qq邮箱要求是https连接，所以需要用SMTP_SSL
        smtp_obj.connect(MAIL['host'], MAIL['port'])    # 设置SMTP地址和端口号
        smtp_obj.login(MAIL['username'], MAIL['password'])
        smtp_obj.sendmail(sender, receivers, message.as_string())
    except smtplib.SMTPException as e:
        logger.error(u"无法发送邮件: {}".format(e))

if __name__ == '__main__':
    content = make_content()
    send_email(RECEIVERS, content)



<p align="center">
  <img src="https://raw.githubusercontent.com/521xueweihan/img_logo/master/logo/readme.gif"/>
  <br>中文 | <a href="README_en.md">English</a> | <a href="README_ja.md">日本語</a>
  <br>分享 GitHub 上有趣、入门级的开源项目。<br>兴趣是最好的老师，这里能够帮你找到编程的兴趣！
</p>

<p align="center">
  <a href="https://raw.githubusercontent.com/521xueweihan/img_logo/master/logo/weixin.png"><img src="https://img.shields.io/badge/Talk-%E5%BE%AE%E4%BF%A1%E7%BE%A4-brightgreen.svg?style=popout-square" alt="WeiXin"></a>
  <a href="https://github.com/521xueweihan/HelloGitHub/stargazers"><img src="https://img.shields.io/github/stars/521xueweihan/HelloGitHub.svg?style=popout-square" alt="GitHub stars"></a>
  <a href="https://github.com/521xueweihan/HelloGitHub/issues"><img src="https://img.shields.io/github/issues/521xueweihan/HelloGitHub.svg?style=popout-square" alt="GitHub issues"></a>
    <a href="https://weibo.com/hellogithub"><img src="https://img.shields.io/badge/%E6%96%B0%E6%B5%AA-Weibo-red.svg?style=popout-square" alt="Sina Weibo"></a>
</p>

## 简介

HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。**每月 28 号**以月刊的形式[更新发布](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA5MzYyNzQ0MQ==&action=getalbum&album_id=1331197538447310849#wechat_redirect)，内容包括：**有趣、入门级的开源项目**、**开源书籍**、**实战项目**、**企业级项目**等，让你用很短时间感受到开源的魅力，爱上开源！

## 内容
获得更好的阅读体验 [官网](https://hellogithub.com/) 或 [HelloGitHub 公众号](https://cdn.jsdelivr.net/gh/521xueweihan/img_logo@main/logo/weixin.png)

| :card_index: | :jack_o_lantern: | :beer: | :fish_cake: | :octocat: |
| ------- | ----- | ------------ | ------ | --------- |
| [第 101 期](/content/HelloGitHub101.md) |
| [第 100 期](/content/HelloGitHub100.md) | [第 99 期](/content/HelloGitHub99.md) | [第 98 期](/content/HelloGitHub98.md) | [第 97 期](/content/HelloGitHub97.md) | [第 96 期](/content/HelloGitHub96.md) |
| [第 95 期](/content/HelloGitHub95.md) | [第 94 期](/content/HelloGitHub94.md) | [第 93 期](/content/HelloGitHub93.md) | [第 92 期](/content/HelloGitHub92.md) | [第 91 期](/content/HelloGitHub91.md) |
| [第 90 期](/content/HelloGitHub90.md) | [第 89 期](/content/HelloGitHub89.md) | [第 88 期](/content/HelloGitHub88.md) | [第 87 期](/content/HelloGitHub87.md) | [第 86 期](/content/HelloGitHub86.md) |
| [第 85 期](/content/HelloGitHub85.md) | [第 84 期](/content/HelloGitHub84.md) | [第 83 期](/content/HelloGitHub83.md) | [第 82 期](/content/HelloGitHub82.md) | [第 81 期](/content/HelloGitHub81.md) |
| [第 80 期](/content/HelloGitHub80.md) | [第 79 期](/content/HelloGitHub79.md) | [第 78 期](/content/HelloGitHub78.md) | [第 77 期](/content/HelloGitHub77.md) | [第 76 期](/content/HelloGitHub76.md) |
| [第 75 期](/content/HelloGitHub75.md) | [第 74 期](/content/HelloGitHub74.md) | [第 73 期](/content/HelloGitHub73.md) | [第 72 期](/content/HelloGitHub72.md) | [第 71 期](/content/HelloGitHub71.md) |
| [第 70 期](/content/HelloGitHub70.md) | [第 69 期](/content/HelloGitHub69.md) | [第 68 期](/content/HelloGitHub68.md) | [第 67 期](/content/HelloGitHub67.md) | [第 66 期](/content/HelloGitHub66.md) |


欢迎[推荐或自荐](https://hellogithub.com/periodical)项目成为 **HelloGitHub** 的[贡献者](https://github.com/521xueweihan/HelloGitHub/blob/master/content/contributors.md)

## 赞助


<table>
  <thead>
    <tr>
      <th align="center" style="width: 80px;">
        <a href="https://www.ucloud.cn/site/active/gpu.html?utm_term=logo&utm_campaign=hellogithub&utm_source=otherdsp&utm_medium=display&ytag=logo_hellogithub_otherdsp_display">
          <img src="https://raw.githubusercontent.com/521xueweihan/img_logo/master/logo/ucloud.png" width="60px"><br>
          <sub>UCloud</sub><br>
          <sub>超值的GPU云服务</sub>
        </a>
      </th>
      <th align="center" style="width: 80px;">
        <a href="https://www.upyun.com/?from=hellogithub">
          <img src="https://raw.githubusercontent.com/521xueweihan/img_logo/master/logo/upyun.png" width="60px"><br>
          <sub>CDN</sub><br>
          <sub>开启全网加速</sub>
        </a>
      </th>
      <th align="center" style="width: 80px;">
        <a href="https://github.com/OpenIMSDK/Open-IM-Server">
          <img src="https://raw.githubusercontent.com/521xueweihan/img_logo/master/logo/im.png" width="60px"><br>
          <sub>OpenIM</sub><br>
          <sub>开源IM力争No.1</sub>
        </a>
      </th>
      <th align="center" style="width: 80px;">
        <a href="https://apifox.cn/a103hello">
          <img src="https://raw.githubusercontent.com/521xueweihan/img_logo/master/logo/apifox.png" width="60px"><br>
          <sub>Apifox</sub><br>
          <sub>比 Postman 更强大</sub>
        </a>
      </th>
    </tr>
  </thead>
</table>


## 声明

<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh"><img alt="知识共享许可协议" style="border-width: 0" src="https://licensebuttons.net/l/by-nc-nd/4.0/88x31.png"></a><br>本作品采用 <a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh">署名-非商业性使用-禁止演绎 4.0 国际</a> 进行许可。<a href="mailto:595666367@qq.com">联系我</a>
