# 怎样获得 GitHub 的 stars 列表

一直有一个想法：把 GitHub
上加星的仓库做成一个列表或者其他便于浏览查找的形式。今天，我就发现了这种工具。趁着午休，把它用上了，展示仓库：[tianheg/stars](https://github.com/tianheg/stars)。

该仓库中包含的这个列表是基于一个名为 `starred` 的 pip 包生成的，再通过
GitHub Action
持续集成，达到每天自动生成列表的目的。该仓库地址：[maguowei/starred](https://github.com/maguowei/starred)。

接下来记录整个过程：

### 一、 {#一}

新建一个名字任意的仓库，新建文件名为 `ci.yml` 路径为
=~/.github/workflows/ci.yml= ，并存放以下内容：

```
    name: update stars # GitHub Action 的名字
    on:
      workflow_dispatch: # 为了手动部署，查看运行过程
      schedule:
        - cron: '00 0 * * *' # 定时：此时的时间是 08:00 CST – China Standard Time
    jobs:
      awesome-stars:
        name: update awesome-stars
        runs-on: ubuntu-latest # 运行在最新的 Ubuntu 环境中，即 Ubuntu 20.04
        steps:
          - uses: actions/checkout@v1
          - name: Set up Python
            uses: actions/setup-python@v1
            with:
              python-version: 3.7 # 安装 3.7 版本的 python
          - name: Install dependencies # 更新 pip，安装 starred 包
            run: |
              python -m pip install --upgrade pip
              pip install starred
          - name: update repo
            env:
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
            run: starred --username tianheg --repository stars --sort --token ${GITHUB_TOKEN} --message 'stars update by github actions cron' # 执行生成 stars 列表的操作
```

手动运行该 GitHub Action，即生成一个属于你自己的 stars 列表，这是我的：

{{< figure src="/get-github-stars.png" >}}

### 二、 {#二}

我们来读读 `starred`
的代码，地址：[maguowei/starred/blob/master/starred/starred.py](https://github.com/maguowei/starred/blob/master/starred/starred.py)：

```python
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    import sys
    from io import BytesIO
    from collections import OrderedDict
    import click
    from github3 import GitHub
    from github3.exceptions import NotFoundError
    from starred import VERSION

    desc = '''# Awesome Stars [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d730\
    5f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome)

    > A curated list of my GitHub stars!  Generated by [starred](https://github.com/maguowei/starred)

    ## Contents
    '''

    license_ = '''
    ## License

    [![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)]\
    (https://creativecommons.org/publicdomain/zero/1.0/)

    To the extent possible under law, [{username}](https://github.com/{username})\
     has waived all copyright and related or neighboring rights to this work.
    '''

    html_escape_table = {
        ">": "&gt;",
        "<": "&lt;",
    }

    def html_escape(text):
        """Produce entities within text."""
        return "".join(html_escape_table.get(c, c) for c in text)

    @click.command()
    @click.option('--username', envvar='USER', help='GitHub username')
    @click.option('--token', envvar='GITHUB_TOKEN', help='GitHub token')
    @click.option('--sort',  is_flag=True, help='sort by language')
    @click.option('--repository', default='', help='repository name')
    @click.option('--message', default='update stars', help='commit message')
    @click.version_option(version=VERSION, prog_name='starred')
    def starred(username, token, sort, repository, message):
        """GitHub starred

        creating your own Awesome List used GitHub stars!

        example:
            starred --username maguowei --sort > README.md
        """
        if repository:
            if not token:
                click.secho('Error: create repository need set --token', fg='red')
                return
            file = BytesIO()
            sys.stdout = file
        else:
            file = None

        gh = GitHub(token=token)
        stars = gh.starred_by(username)
        click.echo(desc)
        repo_dict = {}

        for s in stars:
            language = s.language or 'Others'
            description = html_escape(s.description).replace('\n', '') if s.description else ''
            if language not in repo_dict:
                repo_dict[language] = []
            repo_dict[language].append([s.name, s.html_url, description.strip()])

        if sort:
            repo_dict = OrderedDict(sorted(repo_dict.items(), key=lambda l: l[0]))

        for language in repo_dict.keys():
            data = u'  - [{}](#{})'.format(language, '-'.join(language.lower().split()))
            click.echo(data)
        click.echo('')

        for language in repo_dict:
            click.echo('## {} \n'.format(language.replace('#', '# #')))
            for repo in repo_dict[language]:
                data = u'- [{}]({}) - {}'.format(*repo)
                click.echo(data)
            click.echo('')

        click.echo(license_.format(username=username))

        if file:
            try:
                rep = gh.repository(username, repository)
                readme = rep.readme()
                readme.update(message, file.getvalue())
            except NotFoundError:
                rep = gh.create_repository(repository, 'A curated list of my GitHub stars!')
                rep.create_file('README.md', 'starred initial commit', file.getvalue())
            click.launch(rep.html_url)

    if __name__ == '__main__':
        starred()
```

- 4 - 10 行是引用的类库
- 13 - 30 是生成的 README.md 中的文字叙述
- 42 - 48 是命令指示

### 使用的资源 {#使用的资源}

- [maguowei/starred](https://github.com/maguowei/starred)
- [maguowei/awesome-stars](https://github.com/maguowei/awesome-stars)
