├── .github └── workflows │ └── opml.yml ├── .gitignore ├── README.md └── opml.py /.github/workflows/opml.yml: -------------------------------------------------------------------------------- 1 | name: OPML Generate 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up Python 3.10 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: "3.10" 23 | - name: Prepare output folder 24 | if: ${{ github.ref == 'refs/heads/master' }} 25 | run: | 26 | git clone --no-checkout --depth=1 --branch=opml --single-branch \ 27 | "https://${GITHUB_ACTOR}:${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git" \ 28 | opml 29 | - name: Generate opml files 30 | run: | 31 | mkdir -p opml 32 | python opml.py --output ./opml/ustclug.opml 33 | python opml.py --thunderbird --output ./opml/ustclug-thunderbird.opml 34 | - name: Publish to "opml" branch 35 | if: ${{ github.ref == 'refs/heads/master' }} 36 | run: | 37 | git -C opml add --all 38 | if ! git -C opml diff-index --quiet HEAD --; then 39 | git -C opml -c user.name="GitHub Actions autobuild" -c user.email="noreply@github.com" commit \ 40 | -m "Update OPML on $(date +%Y-%m-%d)" -m "$(git log -1 --pretty='tformat:[%h] %an: %s' HEAD)" 41 | git -C opml push 42 | fi 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ustclug.opml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blogs 2 | 3 | USTCLUG 同学们的博客列表,参考了 TUNA 协会的 [blogroll](https://github.com/tuna/blogroll) 项目。 4 | 5 | 可以通过发 Issues 或 Pull requests 的方式加入此列表,需要附上你的博客的名字、URL 与 RSS 订阅方式,如果能介绍一下你的博客的内容就更好了。 6 | 7 | 以下列表按照添加时间顺序排列。 8 | 9 | ## 列表 10 | 11 | | 昵称/博客名称 | URL | RSS | 12 | | -------------- | -------------------------------- | ---------------------------------------- | 13 | | taoky | https://blog.taoky.moe | https://blog.taoky.moe/feed.xml | 14 | | iBug | https://ibug.io/blog | https://ibug.io/feed.xml | 15 | | zml | https://mlzeng.com/ | https://mlzeng.com/index.xml | 16 | | sirius | https://sirius1242.github.io/ | https://sirius1242.github.io/feed.xml | 17 | | nicecream | https://voile.tech | | 18 | | Elisa's Blog | https://eltsai.github.io/ | https://eltsai.github.io/feed.xml | 19 | | 负一的平方根 | https://sqrt-1.me/ | https://sqrt-1.me/?feed=rss2 | 20 | | volltin | https://blog.volltin.com | https://blog.volltin.com/feed/ | 21 | | elliot(ertuil) | https://www.elliot98.top | https://www.elliot98.top/index.xml | 22 | | 酱瓜 / Jonbgua | https://jonbgua.com | https://www.jonbgua.com/atom.xml | 23 | | ksqsf / Cicada | https://ksqsf.moe/blog/ | https://ksqsf.moe/index.xml | 24 | | ustcpetergu | https://regymm.github.io/MyBlog/ | https://regymm.github.io/MyBlog/feed.xml | 25 | | lys | https://liyishuai.wordpress.com | https://liyishuai.wordpress.com/feed/ | 26 | | libreliu | https://blog.libreliu.info | https://blog.libreliu.info/atom.xml | 27 | | Bowen's Blog | https://foreverfancy.github.io/ | https://foreverfancy.github.io/atom.xml | 28 | | myl7 | https://myl.moe | https://myl.moe/rss.xml | 29 | | totoro | https://yyw.moe | https://yyw.moe/atom.xml | 30 | | boj | https://ring0.me/ | https://ring0.me/atom.xml | 31 | | bcli | https://bc-li.github.io/ | https://bc-li.github.io/feed.xml | 32 | | Catoverflow | https://c-j.dev/ | https://c-j.dev/atom.xml | 33 | | zhao's Moments | https://zhaozuohong.vip/ | https://zhaozuohong.vip/feed.xml | 34 | | Zigone | https://zigone.site | https://zigone.site/atom.xml | 35 | | 忧郁沙茶 | https://crabtux.github.io/ | https://crabtux.github.io/atom.xml | 36 | | Hanako | https://hanako.me/ | https://hanako.me/rss.xml | 37 | | Tianyi Cui | https://blog.ctyi.me/ | https://blog.ctyi.me/feed.xml | 38 | | PRO's Blog | https://pro-2684.github.io/ | | 39 | | Monsoon | https://monsoon-cs.moe/ | https://monsoon-cs.moe/atom.xml | 40 | | llyのblog | https://blog.liuly.moe/ | https://blog.liuly.moe/feed.xml | 41 | | cuihao (cvhc) | https://blog.cvhc.cc/ | | 42 | | jenny42 | https://jenny42.com/ | https://jenny42.com/atom.xml | 43 | | GWDx | https://gwdx.github.io/ | https://gwdx.github.io/index.xml | 44 | 45 | ## OPML 文件 46 | 47 | 使用仓库下 `opml.py` 脚本自动生成的 OPML 文件位于 `opml` 分支,可以使用支持的软件订阅或导入。 48 | -------------------------------------------------------------------------------- /opml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script does not require third-patry packages 4 | 5 | import argparse 6 | import sys 7 | import urllib.request 8 | from xml.sax.saxutils import quoteattr 9 | from pathlib import Path 10 | from io import StringIO 11 | 12 | 13 | def check(rssurl): 14 | req = urllib.request.Request(rssurl) 15 | req.add_header("User-Agent", "RSS OPML Generator") 16 | urllib.request.urlopen(req).read().decode("utf-8") 17 | 18 | 19 | def main(args): 20 | # 0. Open output file 21 | # We should open the output file in binary mode to prevent Windows converting LF to CRLF 22 | with open(args.output, "wb") as output: 23 | # Mimic the print function to binary file 24 | def oprint(*args, **kwargs): 25 | # Output to a string first 26 | s = StringIO() 27 | print(*args, **kwargs, file=s) 28 | # Then get it to the actual binary file 29 | s = s.getvalue().encode("utf-8") 30 | output.write(s) 31 | 32 | contents = [] 33 | # 1. Parse README.md and get the list of links 34 | with open("./README.md", encoding="utf-8") as f: 35 | for line in f: 36 | if line.startswith("|"): 37 | try: 38 | name, url, rssurl = line.split("|")[1:4] 39 | name = name.strip() 40 | url = url.strip() 41 | rssurl = rssurl.strip() 42 | # is rssurl valid? 43 | if not rssurl.lower().startswith("http"): 44 | continue 45 | name = quoteattr(name) 46 | contents.append((name, url, rssurl)) 47 | except ValueError: 48 | pass 49 | # 2. Generate OPML 50 | oprint("") 51 | oprint("") 52 | oprint("USTCLUG Blogs") 53 | oprint("") 54 | oprint("") 55 | 56 | for name, url, rssurl in contents: 57 | skip = False 58 | if args.ignore_unavailable_rss: 59 | try: 60 | check(rssurl) 61 | except: 62 | oprint( 63 | "".format( 64 | name, rssurl, url 65 | ) 66 | ) 67 | oprint( 68 | "name: {}, url: {}, rssurl: {} not available".format( 69 | name, url, rssurl 70 | ), 71 | file=sys.stderr, 72 | ) 73 | skip = True 74 | if not skip: 75 | if args.thunderbird: 76 | oprint("".format(name)) 77 | oprint( 78 | "".format( 79 | name, quoteattr(rssurl), quoteattr(url) 80 | ) 81 | ) 82 | if args.thunderbird: 83 | oprint("") 84 | 85 | oprint("") 86 | oprint("") 87 | 88 | 89 | if __name__ == "__main__": 90 | parser = argparse.ArgumentParser("Blogs OPML Generator") 91 | parser.add_argument( 92 | "--ignore-unavailable-rss", 93 | action="store_true", 94 | help="Ignore unavailable RSS feeds", 95 | ) 96 | parser.add_argument( 97 | "--thunderbird", 98 | action="store_true", 99 | help="Generate Thunderbird-compatible OPML", 100 | ) 101 | # UTF-8 support of Windows Console is completely a mess 102 | # And some apps like Thunderbird requires a UTF-8 opml file 103 | # So I have to do output within the script instead of using shell redirection 104 | # See https://github.com/ustclug/blogs/issues/5 for more detailss 105 | parser.add_argument( 106 | "--output", type=Path, help="Output opml file path", default="ustclug.opml" 107 | ) 108 | args = parser.parse_args() 109 | main(args) 110 | --------------------------------------------------------------------------------