├── requirements.txt ├── vercel.json ├── Pipfile ├── .gitignore ├── api └── index.py ├── trending.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml==4.8.0 2 | Flask==2.2.2 3 | asgiref 4 | flask[async] 5 | requests==2.28.2 -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/(.*)", "destination": "/api/index" }] 3 | } 4 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | flask = "*" 8 | 9 | [requires] 10 | python_version = "3.9" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .venv 3 | __pycache__/* 4 | .idea/* 5 | 6 | .vercel 7 | *.log 8 | *.pyc 9 | 10 | # Environments 11 | .env 12 | .venv 13 | env/ 14 | venv/ 15 | ENV/ 16 | env.bak/ 17 | venv.bak/ 18 | 19 | .vercel 20 | 21 | .vercel 22 | -------------------------------------------------------------------------------- /api/index.py: -------------------------------------------------------------------------------- 1 | from flask import Flask,jsonify,request 2 | 3 | from trending import NO_RESULT, REPOSITORY, get_all_language, get_trending 4 | 5 | app = Flask(__name__) 6 | 7 | @app.route('/') 8 | def home(): 9 | return 'Hello github trending!' 10 | 11 | @app.route('/lang') 12 | async def lang(): 13 | langs = await get_all_language() 14 | size = len(langs) 15 | if size > 0: 16 | return jsonify({ 17 | 'msg': 'suc', 18 | 'count': size, 19 | 'items': [x.replace('\n', '').strip() for x in langs], 20 | }), 201 21 | else: 22 | return jsonify(NO_RESULT), 404 23 | 24 | @app.route('/repo') 25 | async def repo(): 26 | return await trending(request.args, REPOSITORY) 27 | 28 | 29 | async def trending(args, start_url: str): 30 | lang = args.get("lang", None) 31 | since = args.get("since", None) 32 | url = start_url 33 | if lang is not None: 34 | lang = lang.replace('-shuo', '%23') 35 | url += lang 36 | params = None 37 | if since is not None: 38 | params = {'since': since} 39 | result = await get_trending(url=url, params=params) 40 | if result['count'] > 0: 41 | return jsonify(result), 201 42 | else: 43 | return jsonify(result), 404 44 | -------------------------------------------------------------------------------- /trending.py: -------------------------------------------------------------------------------- 1 | from lxml import etree 2 | import requests 3 | 4 | GITHUB_URL = 'https://github.com/' 5 | REPOSITORY = GITHUB_URL + 'trending/' 6 | USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 ' \ 7 | 'Safari/537.36 ' 8 | HEADER = {'User-Agent': USER_AGENT} 9 | TIMEOUT = 9 10 | NO_RESULT = { 11 | 'count': 0, 12 | 'msg': 'Unavailable', 13 | 'items': [], 14 | } 15 | 16 | async def get_trending(url: str, params: dict = None) -> dict: 17 | html = await get_html(url, params) 18 | if html: 19 | is_blank = await has_trending(html) 20 | if not is_blank: 21 | return await parse_repo(html) 22 | else: 23 | return NO_RESULT 24 | else: 25 | return NO_RESULT 26 | 27 | 28 | async def parse_repo(html) -> dict: 29 | items = [] 30 | articles = html.xpath('//article') 31 | for article in articles: 32 | item = {'repo': article.xpath('./h1/a/@href')[0][1:]} 33 | item['repo_link'] = GITHUB_URL + item['repo'] 34 | tmp = article.xpath('./p/text()') 35 | item['desc'] = tmp[0].replace('\n', '').strip() if len(tmp) > 0 else '' 36 | tmp = article.xpath('./div[last()]/span[1]/span[2]/text()') 37 | item['lang'] = tmp[0].replace('\n', '').strip() if len(tmp) > 0 else '' 38 | tmp = article.xpath('./div[last()]/a[1]/text()') 39 | item['stars'] = "".join(tmp).replace(' ', '').replace('\n', '') 40 | tmp = article.xpath('./div[last()]/a[2]/text()') 41 | item['forks'] = "".join(tmp).replace(' ', '').replace('\n', '') 42 | tmp = article.xpath('./div[last()]/span[3]/text()') 43 | item['added_stars'] = "".join(tmp).replace('\n', '').strip() 44 | item['avatars'] = article.xpath('./div[last()]/span[2]/a/img/@src') 45 | items.append(item) 46 | return { 47 | 'count': len(items), 48 | 'msg': 'suc', 49 | 'items': items 50 | } 51 | 52 | 53 | async def has_trending(html): 54 | blank = html.xpath('//div[contains(@class,"blankslate")]') 55 | if blank or len(blank) > 0: 56 | return html.xpath('string(//div[contains(@class,"blankslate")]/h3)') \ 57 | .replace('\n', '').strip() 58 | else: 59 | return None 60 | 61 | 62 | async def get_html(url: str, params: dict = None): 63 | try: 64 | if params is not None: 65 | url = "{0}?since={1}".format(url, params.get('since')) 66 | response = requests.get(url=url, headers=HEADER, timeout=TIMEOUT) 67 | except Exception: 68 | return None 69 | else: 70 | return etree.HTML(response.text) 71 | 72 | 73 | async def get_all_language() -> list: 74 | html = await get_html(url=REPOSITORY) 75 | return html.xpath('//div[@class="select-menu-list"]/div/a/span/text()') 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

Github Trending Repos API

3 |

4 | A REST API to fetch a list of trending GitHub repositories
5 | A fork of xdgongs/github-trending, but with CORS enabled 6 |

7 |

8 | 9 |

10 | 11 | > [!WARNING] 12 | > Heroku has recently removed all free plans, and bumped up their pricing 😔 13 | > Therefore the API is temporarily offline, while I find an alternative platform, suggestions welcome! 14 | 15 | ## Usage 16 | 17 | Requests can be made to: `https://gh-trending-repos.herokuapp.com/repo`. No auth is required. 18 | 19 | ### Parameters 20 | 21 | | Name | Type | Description | 22 | |-------|--------|------------------------------------------------------------------------------| 23 | | lang | string | *Optional* - The language of trending repository. Do not include `#` characters | 24 | | since | string | *Optional* - The timeframe, can be either `daily`, `weekly` or `monthly`. Defaults to `daily` | 25 | 26 | For example request this address: `https://gh-trending-repos.herokuapp.com/repo?lang=java&since=weekly` 27 | 28 |
Example Response 29 | 30 | ```json 31 | { 32 | "count": 25, 33 | "msg": "suc", 34 | "items": [ 35 | { 36 | "repo": "TencentARC/GFPGAN", 37 | "repo_link": "https://github.com/TencentARC/GFPGAN", 38 | "desc": "GFPGAN aims at developing Practical Algorithms for Real-world Face Restoration.", 39 | "lang": "Python", 40 | "stars": "10,767", 41 | "forks": "1,635", 42 | "added_stars": "5,356 stars this week", 43 | "avatars": [ 44 | "https://avatars.githubusercontent.com/u/17445847?s=40&v=4", 45 | "https://avatars.githubusercontent.com/u/81195143?s=40&v=4", 46 | "https://avatars.githubusercontent.com/u/18028233?s=40&v=4", 47 | "https://avatars.githubusercontent.com/u/36897236?s=40&v=4", 48 | "https://avatars.githubusercontent.com/u/17243165?s=40&v=4" 49 | ] 50 | }, 51 | { 52 | "repo": "dendibakh/perf-book", 53 | "repo_link": "https://github.com/dendibakh/perf-book", 54 | "desc": "The book \"Performance Analysis and Tuning on Modern CPU\"", 55 | "lang": "TeX", 56 | "stars": "759", 57 | "forks": "47", 58 | "added_stars": "445 stars this week", 59 | "avatars": [ 60 | "https://avatars.githubusercontent.com/u/4634056?s=40&v=4" 61 | ] 62 | } 63 | ... 64 | ] 65 | } 66 | 67 | ``` 68 | 69 |
70 | 71 | ## Deployment 72 | 73 | Deploy to Heroku: 74 | 75 | 76 | Deploy to Heroku 77 | 78 | 79 | Or, Run locally: 80 | 81 | - Get the code: `git clone https://github.com/Lissy93/gh-trending-no-cors.git` 82 | - Navigate into directory: `cd gh-trending-no-cors` 83 | - Install dependencies: `pip install -r requirements.txt` 84 | - Start the web server: `python manage.py --port=8080` 85 | - Then open your Postman or your browser, and visit `http://localhost:8080/repo` 86 | 87 | ## Info 88 | 89 | ### Contributing 90 | 91 | Pull requests are welcome :) 92 | 93 | ### Dependencies 94 | 95 | - [lxml] - Python XML toolkit 96 | - [tornado] - Web framework, developed by FriendFeed 97 | - [ustudio-tornado-cors] - Adds CORS support to Tornado, by @ustudio 98 | 99 | ### Credits 100 | 101 | Full credit to the author of the original repo, @Edgar 102 | 103 | ### Privacy 104 | 105 | See the [Heroku/ Salesforce Privacy Policy]() for the hosted instance, and the [GitHub Privacy Statement]() for the data fetched from the GH API. 106 | 107 | ## License 108 | 109 | This fork is licensed under MIT - © Alicia Sykes 2021 110 | --------------------------------------------------------------------------------