add cninfo announcement

This commit is contained in:
hillerliao
2019-12-15 21:52:58 +08:00
parent 7dc1508458
commit 65a4a5b7a8
24 changed files with 639 additions and 573 deletions

View File

@@ -1,2 +1,2 @@
[run] [run]
source = rsshub source = rsshub

View File

@@ -1,3 +1,3 @@
FLASK_ENV=development FLASK_ENV=development
FLASK_APP=rsshub FLASK_APP=rsshub
FLASK_DEBUG=1 FLASK_DEBUG=1

32
.gitignore vendored
View File

@@ -1,17 +1,17 @@
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
.pytest_cache .pytest_cache
# Distribution / packaging # Distribution / packaging
build/ build/
dist/ dist/
*.egg-info/ *.egg-info/
.idea .idea
venv venv
# Others # Others
.vscode .vscode
.coverage .coverage
htmlcov/ htmlcov/
data-dev.db data-dev.db

View File

@@ -1,70 +1,70 @@
import os import os
from datetime import datetime from datetime import datetime
import click import click
from flask import Flask, render_template from flask import Flask, render_template
from flask.cli import with_appcontext from flask.cli import with_appcontext
from rsshub.config import config from rsshub.config import config
from rsshub.extensions import * from rsshub.extensions import *
from rsshub.blueprints.main import bp as main_bp from rsshub.blueprints.main import bp as main_bp
from rsshub.utils import XMLResponse from rsshub.utils import XMLResponse
def create_app(config_name=None): def create_app(config_name=None):
if config_name is None: if config_name is None:
config_name = os.getenv('FLASK_CONFIG', 'development') config_name = os.getenv('FLASK_CONFIG', 'development')
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config[config_name]) app.config.from_object(config[config_name])
app.response_class = XMLResponse app.response_class = XMLResponse
register_blueprints(app) register_blueprints(app)
register_extensions(app) register_extensions(app)
register_errors(app) register_errors(app)
register_context_processors(app) register_context_processors(app)
register_cli(app) register_cli(app)
return app return app
def register_extensions(app): def register_extensions(app):
bootstrap.init_app(app) bootstrap.init_app(app)
debugtoolbar.init_app(app) debugtoolbar.init_app(app)
moment.init_app(app) moment.init_app(app)
def register_blueprints(app): def register_blueprints(app):
app.register_blueprint(main_bp) app.register_blueprint(main_bp)
def register_errors(app): def register_errors(app):
@app.errorhandler(400) @app.errorhandler(400)
def bad_request(e): def bad_request(e):
return render_template('errors/400.html'), 400 return render_template('errors/400.html'), 400
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
return render_template('errors/404.html'), 404 return render_template('errors/404.html'), 404
@app.errorhandler(500) @app.errorhandler(500)
def internal_server_error(e): def internal_server_error(e):
return render_template('errors/500.html'), 500 return render_template('errors/500.html'), 500
def register_context_processors(app): def register_context_processors(app):
@app.context_processor @app.context_processor
def inject_date_now(): def inject_date_now():
now = datetime.utcnow() now = datetime.utcnow()
return {'now': now} return {'now': now}
def register_cli(app): def register_cli(app):
@app.cli.command() @app.cli.command()
@with_appcontext @with_appcontext
def ptshell(): def ptshell():
"""Use ptpython as shell.""" """Use ptpython as shell."""
try: try:
from ptpython.repl import embed from ptpython.repl import embed
if not app.config['TESTING']: if not app.config['TESTING']:
embed(app.make_shell_context()) embed(app.make_shell_context())
except ImportError: except ImportError:
click.echo('ptpython not installed! Use the default shell instead.') click.echo('ptpython not installed! Use the default shell instead.')

View File

@@ -1,52 +1,59 @@
from flask import Blueprint, render_template, request from flask import Blueprint, render_template, request
bp = Blueprint('main', __name__) bp = Blueprint('main', __name__)
@bp.route('/') @bp.route('/')
def index(): def index():
return render_template('main/index.html') return render_template('main/index.html')
@bp.route('/feeds') @bp.route('/feeds')
def feeds(): def feeds():
return render_template('main/feeds.html') return render_template('main/feeds.html')
@bp.app_template_global() @bp.app_template_global()
def filter_content(ctx): def filter_content(ctx):
include_title = request.args.get('include_title') include_title = request.args.get('include_title')
include_description = request.args.get('include_description') include_description = request.args.get('include_description')
exclude_title = request.args.get('exclude_title') exclude_title = request.args.get('exclude_title')
exclude_description = request.args.get('exclude_description') exclude_description = request.args.get('exclude_description')
limit = request.args.get('limit', type=int) limit = request.args.get('limit', type=int)
items = ctx['items'].copy() items = ctx['items'].copy()
items = [item for item in items if include_title in item['title']] if include_title else items items = [item for item in items if include_title in item['title']] if include_title else items
items = [item for item in items if include_description in item['description']] if include_description else items items = [item for item in items if include_description in item['description']] if include_description else items
items = [item for item in items if exclude_title not in item['title']] if exclude_title else items items = [item for item in items if exclude_title not in item['title']] if exclude_title else items
items = [item for item in items if exclude_description not in item['description']] if exclude_description else items items = [item for item in items if exclude_description not in item['description']] if exclude_description else items
items = items[:limit] if limit else items items = items[:limit] if limit else items
ctx = ctx.copy() ctx = ctx.copy()
ctx['items'] = items ctx['items'] = items
return ctx return ctx
#---------- feed路由从这里开始 -----------# #---------- feed路由从这里开始 -----------#
@bp.route('/chuansongme/articles/<string:category>') @bp.route('/cninfo/announcement/<string:stock_id>/<string:category>')
@bp.route('/chuansongme/articles') @bp.route('/cninfo/announcement')
def chuansongme_articles(category=''): def cninfo_announcement(stock_id='', category=''):
from rsshub.spiders.chuansongme.articles import ctx from rsshub.spiders.cninfo.announcement import ctx
return render_template('main/atom.xml', **filter_content(ctx(category))) return render_template('main/atom.xml', **filter_content(ctx(stock_id,category)))
@bp.route('/ctolib/topics/<string:category>') @bp.route('/chuansongme/articles/<string:category>')
@bp.route('/ctolib/topics') @bp.route('/chuansongme/articles')
def ctolib_topics(category=''): def chuansongme_articles(category=''):
from rsshub.spiders.ctolib.topics import ctx from rsshub.spiders.chuansongme.articles import ctx
return render_template('main/atom.xml', **filter_content(ctx(category))) return render_template('main/atom.xml', **filter_content(ctx(category)))
@bp.route('/infoq/recommend') @bp.route('/ctolib/topics/<string:category>')
def infoq_recommend(): @bp.route('/ctolib/topics')
from rsshub.spiders.infoq.recommend import ctx def ctolib_topics(category=''):
return render_template('main/atom.xml', **filter_content(ctx())) from rsshub.spiders.ctolib.topics import ctx
return render_template('main/atom.xml', **filter_content(ctx(category)))
@bp.route('/infoq/recommend')
def infoq_recommend():
from rsshub.spiders.infoq.recommend import ctx
return render_template('main/atom.xml', **filter_content(ctx()))

View File

@@ -1,32 +1,32 @@
import os import os
import sys import sys
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
class BaseConfig: class BaseConfig:
SITE_NAME = 'RSSHub' SITE_NAME = 'RSSHub'
GITHUB_USERNAME = 'alphardex' GITHUB_USERNAME = 'alphardex'
EMAIL = '2582347430@qq.com' EMAIL = '2582347430@qq.com'
SECRET_KEY = os.environ.get('SECRET_KEY') or 'f43hrt53et53' SECRET_KEY = os.environ.get('SECRET_KEY') or 'f43hrt53et53'
DEBUG_TB_INTERCEPT_REDIRECTS = False DEBUG_TB_INTERCEPT_REDIRECTS = False
class DevelopmentConfig(BaseConfig): class DevelopmentConfig(BaseConfig):
pass pass
class TestingConfig(BaseConfig): class TestingConfig(BaseConfig):
TESTING = True TESTING = True
class ProductionConfig(BaseConfig): class ProductionConfig(BaseConfig):
pass pass
config = { config = {
'development': DevelopmentConfig, 'development': DevelopmentConfig,
'testing': TestingConfig, 'testing': TestingConfig,
'production': ProductionConfig 'production': ProductionConfig
} }

View File

@@ -1,8 +1,8 @@
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
from flask_debugtoolbar import DebugToolbarExtension from flask_debugtoolbar import DebugToolbarExtension
from flask_moment import Moment from flask_moment import Moment
bootstrap = Bootstrap() bootstrap = Bootstrap()
debugtoolbar = DebugToolbarExtension() debugtoolbar = DebugToolbarExtension()
moment = Moment() moment = Moment()

View File

@@ -1,23 +1,23 @@
from rsshub.utils import fetch from rsshub.utils import fetch
domain = 'https://chuansongme.com' domain = 'https://chuansongme.com'
def parse(post): def parse(post):
item = {} item = {}
item['title'] = post.css('a.question_link::text').extract()[-1].strip() item['title'] = post.css('a.question_link::text').extract()[-1].strip()
link = f"{domain}{post.css('a.question_link::attr(href)').extract_first()}" link = f"{domain}{post.css('a.question_link::attr(href)').extract_first()}"
item['link'] = link item['link'] = link
return item return item
def ctx(category=''): def ctx(category=''):
tree = fetch(f"{domain}/{category}") tree = fetch(f"{domain}/{category}")
posts = tree.css('.feed_body .pagedlist_item') posts = tree.css('.feed_body .pagedlist_item')
return { return {
'title': '传送门', 'title': '传送门',
'link': domain, 'link': domain,
'description': '传送门:微信公众号订阅', 'description': '传送门:微信公众号订阅',
'author': 'alphardex', 'author': 'alphardex',
'items': list(map(parse, posts)) 'items': list(map(parse, posts))
} }

View File

@@ -0,0 +1,49 @@
import requests
from rsshub.utils import DEFAULT_HEADERS
domain = 'http://www.cninfo.com.cn'
def parse(post):
item = {}
item['title'] = post['secName'] + '(' + post['secCode'] + ')' + ': ' + post['announcementTitle']
item['description'] = item['title']
item['link'] = 'http://static.cninfo.com.cn/' + post['adjunctUrl']
item['pubDate'] = post['announcementTime']
return item
def ctx(stock_id='', category=''):
stock_id = '' if stock_id == 'all' else stock_id
stock_name = ''
stock_list = requests.get('http://www.cninfo.com.cn/new/data/szse_stock.json', headers=DEFAULT_HEADERS).json()['stockList']
for stock in stock_list:
if stock['code'] == stock_id :
stock_id = stock['orgId']
stock_name = stock['zwjc']
break
import datetime
nowtime = datetime.datetime.now()
deltaday=datetime.timedelta(days=1)
start_date = datetime.datetime.strftime(nowtime- 5 * deltaday, '%Y-%m-%d')
end_date = datetime.datetime.strftime(nowtime + 2 * deltaday, '%Y-%m-%d')
seDate = start_date + '~' + end_date
searchkey = ''
if '_' in category:
searchkey = category.split('_')[-1]
category = category.split('_')[0]
DEFAULT_HEADERS.update({'Referer': domain})
posts = requests.post(f'{domain}/new/hisAnnouncement/query', \
data={'pageSize': '30','tabName':'fulltext', 'plate': '', 'category':f'category_{category}_szsh', \
'secid': stock_id,'seDate':'', 'seDate': seDate, 'searchkey': searchkey }, headers=DEFAULT_HEADERS).json()['announcements']
return {
'title': f'{stock_name}-{category}-公告-巨潮资讯',
'link': f'{domain}/new/commonUrl/pageOfSearch?url=disclosure/list/search&checkedCategory=category_{category}_szsh&searchkey={searchkey}',
'description': f'{stock_name}关于{category}的公告-巨潮资讯',
'author': 'hillerliao',
'items': list(map(parse, posts))
}

View File

@@ -1,23 +1,23 @@
from rsshub.utils import fetch from rsshub.utils import fetch
domain = 'https://www.ctolib.com' domain = 'https://www.ctolib.com'
def parse(post): def parse(post):
item = {} item = {}
item['title'] = post.css('a.title::text').extract_first() item['title'] = post.css('a.title::text').extract_first()
item['description'] = post.css('p.abstract::text').extract_first() item['description'] = post.css('p.abstract::text').extract_first()
item['link'] = f"{domain}{post.css('a.title::attr(href)').extract_first()}" item['link'] = f"{domain}{post.css('a.title::attr(href)').extract_first()}"
return item return item
def ctx(category=''): def ctx(category=''):
tree = fetch(f'{domain}/python/topics/{category}') tree = fetch(f'{domain}/python/topics/{category}')
posts = tree.css('ul.note-list li') posts = tree.css('ul.note-list li')
return { return {
'title': 'CTOLib码库', 'title': 'CTOLib码库',
'link': domain, 'link': domain,
'description': 'Python开发社区', 'description': 'Python开发社区',
'author': 'alphardex', 'author': 'alphardex',
'items': list(map(parse, posts)) 'items': list(map(parse, posts))
} }

View File

@@ -1,24 +1,24 @@
import requests import requests
from rsshub.utils import DEFAULT_HEADERS from rsshub.utils import DEFAULT_HEADERS
domain = 'https://www.infoq.cn' domain = 'https://www.infoq.cn'
def parse(post): def parse(post):
item = {} item = {}
item['title'] = post['article_title'] item['title'] = post['article_title']
item['description'] = f"{post['article_summary']}<br><img referrerpolicy='no-referrer' src={post.get('article_cover')}>" item['description'] = f"{post['article_summary']}<br><img referrerpolicy='no-referrer' src={post.get('article_cover')}>"
item['link'] = f"{domain}/article/{post['uuid']}" item['link'] = f"{domain}/article/{post['uuid']}"
return item return item
def ctx(): def ctx():
DEFAULT_HEADERS.update({'Referer': 'https://www.infoq.cn'}) # 必须设置Referer不然会451错误 DEFAULT_HEADERS.update({'Referer': 'https://www.infoq.cn'}) # 必须设置Referer不然会451错误
posts = requests.post(f'{domain}/public/v1/my/recommond', data={'size': 20}, headers=DEFAULT_HEADERS).json()['data'] posts = requests.post(f'{domain}/public/v1/my/recommond', data={'size': 20}, headers=DEFAULT_HEADERS).json()['data']
return { return {
'title': 'infoq', 'title': 'infoq',
'link': domain, 'link': domain,
'description': 'InfoQ - 促进软件开发领域知识与创新的传播', 'description': 'InfoQ - 促进软件开发领域知识与创新的传播',
'author': 'alphardex', 'author': 'alphardex',
'items': list(map(parse, posts)) 'items': list(map(parse, posts))
} }

View File

@@ -1,48 +1,48 @@
/* global */ /* global */
nav { nav {
margin-bottom: 30px; margin-bottom: 30px;
} }
.jumbotron { .jumbotron {
margin-top: 20px; margin-top: 20px;
padding-top: 38px; padding-top: 38px;
padding-bottom: 38px; padding-bottom: 38px;
} }
.tip { .tip {
/* from github.com */ /* from github.com */
position: relative; position: relative;
padding: 40px; padding: 40px;
text-align: center; text-align: center;
background-color: #fafbfc; background-color: #fafbfc;
border: 1px solid #e1e4e8; border: 1px solid #e1e4e8;
border-radius: 3px; border-radius: 3px;
box-shadow: inset 0 0 10px rgba(27, 31, 35, 0.05); box-shadow: inset 0 0 10px rgba(27, 31, 35, 0.05);
} }
.hide { .hide {
display: none; display: none;
} }
.inline { .inline {
display: inline; display: inline;
} }
.page-header { .page-header {
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
} }
.page-footer { .page-footer {
padding-top: 40px; padding-top: 40px;
} }
/* footer */ /* footer */
footer { footer {
margin: 30px 0; margin: 30px 0;
padding: 20px 0; padding: 20px 0;
border-top: 1px solid #e5e5e5; border-top: 1px solid #e5e5e5;
} }

View File

@@ -1,9 +1,9 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block title %}400 错误{% endblock %} {% block title %}400 错误{% endblock %}
{% block content %} {% block content %}
<div class="jumbotron"> <div class="jumbotron">
<h1>400 Bad Request</h1> <h1>400 Bad Request</h1>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,9 +1,9 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block title %}404 错误{% endblock %} {% block title %}404 错误{% endblock %}
{% block content %} {% block content %}
<div class="jumbotron"> <div class="jumbotron">
<h1>404 Not Found</h1> <h1>404 Not Found</h1>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,9 +1,9 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block title %}500 错误{% endblock %} {% block title %}500 错误{% endblock %}
{% block content %} {% block content %}
<div class="jumbotron"> <div class="jumbotron">
<h1>服务器出错</h1> <h1>服务器出错</h1>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,70 +1,70 @@
{% from 'bootstrap/nav.html' import render_nav_item %} {% from 'bootstrap/nav.html' import render_nav_item %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
{% block head %} {% block head %}
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock title %}</title> <title>{% block title %}{% endblock title %}</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon"> <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon"> <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% block styles %} {% block styles %}
{{ bootstrap.load_css() }} {{ bootstrap.load_css() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
{% endblock styles %} {% endblock styles %}
{% endblock head %} {% endblock head %}
</head> </head>
<body> <body>
{% block nav %} {% block nav %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" role="navigation"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark" role="navigation">
<div class="container"> <div class="container">
<a class="navbar-brand" href="{{url_for('main.index')}}">{{ config['SITE_NAME'] }}</a> <a class="navbar-brand" href="{{url_for('main.index')}}">{{ config['SITE_NAME'] }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNavDropdown"> <div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="nav navbar-nav ml-auto"> <ul class="nav navbar-nav ml-auto">
{{render_nav_item('main.feeds', 'Feeds')}} {{render_nav_item('main.feeds', 'Feeds')}}
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
{% endblock nav %} {% endblock nav %}
<main class="container"> <main class="container">
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
{% for category, message in messages %} {% for category, message in messages %}
<div class="alert alert-dismissable alert-{{ category }}" role="alert"> <div class="alert alert-dismissable alert-{{ category }}" role="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button> <button type="button" class="close" data-dismiss="alert">&times;</button>
{{ message }} {{ message }}
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endwith %} {% endwith %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
{% block footer %} {% block footer %}
<footer> <footer>
<p class="float-left"> <p class="float-left">
<small>&copy; <small>&copy;
Made by <a href="https://github.com/{{config['GITHUB_USERNAME']}}" target="_blank">{{config['GITHUB_USERNAME']}}</a> Made by <a href="https://github.com/{{config['GITHUB_USERNAME']}}" target="_blank">{{config['GITHUB_USERNAME']}}</a>
with <span class="fa fa-heart"></span> with <span class="fa fa-heart"></span>
</small> </small>
</p> </p>
<p class="float-right"> <p class="float-right">
<small><a href="mailto:{{ config['EMAIL'] }}">Contact</a></small> <small><a href="mailto:{{ config['EMAIL'] }}">Contact</a></small>
</p> </p>
</footer> </footer>
{% endblock footer %} {% endblock footer %}
</main> </main>
{% block scripts %} {% block scripts %}
{{ bootstrap.load_js() }} {{ bootstrap.load_js() }}
{{ moment.include_moment() }} {{ moment.include_moment() }}
{{ moment.locale('zh-cn') }} {{ moment.locale('zh-cn') }}
{% endblock %} {% endblock %}
</body> </body>
</html> </html>

View File

@@ -1,22 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"> <feed xmlns="http://www.w3.org/2005/Atom">
<generator>{{config['SITE_NAME']}}</generator> <generator>{{config['SITE_NAME']}}</generator>
<webMaster>{{config['EMAIL']}}</webMaster> <webMaster>{{config['EMAIL']}}</webMaster>
<language>zh-cn</language> <language>zh-cn</language>
<id>{{link}}</id> <id>{{link}}</id>
<title><![CDATA[{{title|safe}}]]></title> <title><![CDATA[{{title|safe}}]]></title>
<link href="{{link}}"/> <link href="{{link}}"/>
<author> <author>
<name><![CDATA[{{author|safe}}]]></name> <name><![CDATA[{{author|safe}}]]></name>
</author> </author>
{% for item in items %} {% for item in items %}
<entry> <entry>
<id>{{item.link}}</id> <id>{{item.link}}</id>
<title><![CDATA[{{item.title|safe}}]]></title> <title><![CDATA[{{item.title|safe}}]]></title>
<published>{{item.pubDate|default(now)}}</published> <published>{{item.pubDate|default(now)}}</published>
<updated>{{item.pubDate|default(now)}}</updated> <updated>{{item.pubDate|default(now)}}</updated>
<link href="{{item.link}}"/> <link href="{{item.link}}"/>
<content type="html" src="{{item.link}}"><![CDATA[{{item.description|safe}}]]></content> <content type="html" src="{{item.link}}"><![CDATA[{{item.description|safe}}]]></content>
</entry> </entry>
{% endfor %} {% endfor %}
</feed> </feed>

View File

@@ -1,67 +1,77 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}All Feeds{% endblock title %} {% block title %}All Feeds{% endblock title %}
{% block content %} {% block content %}
<div class="card text-left"> <div class="card text-left">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">传送门</h4> <h4 class="card-title">传送门</h4>
<h6 class="text-muted">文章 <a href="https://github.com/alphardex" target="_blank" class="badge badge-secondary">by alphardex</a></h6> <h6 class="text-muted">文章 <a href="https://github.com/alphardex" target="_blank" class="badge badge-secondary">by alphardex</a></h6>
<p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/chuansongme/articles" target="_blank">https://rsshub-python.herokuapp.com/chuansongme/articles</a></p> <p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/chuansongme/articles" target="_blank">https://rsshub-python.herokuapp.com/chuansongme/articles</a></p>
<p class="card-text">路由:<code>/chuansongme/articles/:category</code></p> <p class="card-text">路由:<code>/chuansongme/articles/:category</code></p>
<p class="card-text">参数category [默认为“最新”]</p> <p class="card-text">参数category [默认为“最新”]</p>
<table class="table table-bordered table-sm"> <table class="table table-bordered table-sm">
<thead> <thead>
<tr> <tr>
{% for th in ['精选','区块链','汽车','创意科技','媒体达人','电影音乐','娱乐休闲','生活旅行','学习工具','历史读书','金融理财','美食菜谱'] %} {% for th in ['精选','区块链','汽车','创意科技','媒体达人','电影音乐','娱乐休闲','生活旅行','学习工具','历史读书','金融理财','美食菜谱'] %}
<th>{{th}}</th> <th>{{th}}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
{% for td in ['select', 'blockchain', 'auto', 'ideatech', 'newsmedia', 'moviemusic', 'fun', 'lifejourney', 'utility', 'hisbook', 'finance', 'food']%} {% for td in ['select', 'blockchain', 'auto', 'ideatech', 'newsmedia', 'moviemusic', 'fun', 'lifejourney', 'utility', 'hisbook', 'finance', 'food']%}
<td>{{td}}</td> <td>{{td}}</td>
{% endfor %} {% endfor %}
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<br> <br>
<div class="card text-left"> <div class="card text-left">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">CTOLib</h4> <h4 class="card-title">CTOLib</h4>
<h6 class="text-muted">话题 <a href="https://github.com/alphardex" target="_blank" class="badge badge-secondary">by alphardex</a></h6> <h6 class="text-muted">话题 <a href="https://github.com/alphardex" target="_blank" class="badge badge-secondary">by alphardex</a></h6>
<p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/ctolib/topics" target="_blank">https://rsshub-python.herokuapp.com/ctolib/topics</a></p> <p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/ctolib/topics" target="_blank">https://rsshub-python.herokuapp.com/ctolib/topics</a></p>
<p class="card-text">路由:<code>/ctolib/topics/:category</code></p> <p class="card-text">路由:<code>/ctolib/topics/:category</code></p>
<p class="card-text">参数category [默认为“默认排序”]</p> <p class="card-text">参数category [默认为“默认排序”]</p>
<table class="table table-bordered table-sm"> <table class="table table-bordered table-sm">
<thead> <thead>
<tr> <tr>
{% for th in ['最新发布', '优质主题'] %} {% for th in ['最新发布', '优质主题'] %}
<th>{{th}}</th> <th>{{th}}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
{% for td in ['last', 'popular']%} {% for td in ['last', 'popular']%}
<td>{{td}}</td> <td>{{td}}</td>
{% endfor %} {% endfor %}
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<br> <br>
<div class="card text-left"> <div class="card text-left">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">InfoQ</h4> <h4 class="card-title">InfoQ</h4>
<h6 class="text-muted">推荐内容 <a href="https://github.com/alphardex" target="_blank" class="badge badge-secondary">by alphardex</a></h6> <h6 class="text-muted">推荐内容 <a href="https://github.com/alphardex" target="_blank" class="badge badge-secondary">by alphardex</a></h6>
<p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/infoq/recommend" target="_blank">https://rsshub-python.herokuapp.com/infoq/recommend</a></p> <p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/infoq/recommend" target="_blank">https://rsshub-python.herokuapp.com/infoq/recommend</a></p>
<p class="card-text">路由:<code>/infoq/recommend</code></p> <p class="card-text">路由:<code>/infoq/recommend</code></p>
</div> </div>
</div> </div>
<br> <br>
<div class="card text-left">
<div class="card-body">
<h4 class="card-title">巨潮资讯</h4>
<h6 class="text-muted">公司公告 <a href="https://github.com/hillerliao" target="_blank" class="badge badge-secondary">by hillerliao</a></h6>
<p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/cninfo/announcement/all/gqjl" target="_blank">https://rsshub-python.herokuapp.com/cninfo/announcement/all/gqjl</a></p>
<p class="card-text">举例:<a href="https://rsshub-python.herokuapp.com/cninfo/announcement/all/gqbd_预披露" target="_blank">https://rsshub-python.herokuapp.com/cninfo/announcement/all/gqbd_预披露</a>,股权变动类公告中标题含有「预披露」的公告</p>
<p class="card-text">路由:<code>/cninfo/announcement/:stock_id/:category</code></p>
</div>
</div>
<br>
{% endblock content %} {% endblock content %}

View File

@@ -1,13 +1,13 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block title %}Welcome to RSShub!{% endblock title %} {% block title %}Welcome to RSShub!{% endblock title %}
{% block content %} {% block content %}
<div class="jumbotron"> <div class="jumbotron">
<h1 class="display-4">Welcome to RSSHub!</h1> <h1 class="display-4">Welcome to RSSHub!</h1>
<p class="lead">If you see this page, the RSSHub is successfully installed and working.</p> <p class="lead">If you see this page, the RSSHub is successfully installed and working.</p>
<p> <p>
<a class="btn btn-primary btn-lg" role="button" target="_blank" href="https://github.com/alphardex/RSSHub-python">View Source</a> <a class="btn btn-primary btn-lg" role="button" target="_blank" href="https://github.com/alphardex/RSSHub-python">View Source</a>
</p> </p>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,25 +1,25 @@
from flask import Response from flask import Response
import requests import requests
from parsel import Selector from parsel import Selector
DEFAULT_HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'} DEFAULT_HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
class XMLResponse(Response): class XMLResponse(Response):
def __init__(self, response, **kwargs): def __init__(self, response, **kwargs):
if 'mimetype' not in kwargs and 'contenttype' not in kwargs: if 'mimetype' not in kwargs and 'contenttype' not in kwargs:
if response.startswith('<?xml'): if response.startswith('<?xml'):
kwargs['mimetype'] = 'application/xml' kwargs['mimetype'] = 'application/xml'
return super().__init__(response, **kwargs) return super().__init__(response, **kwargs)
def fetch(url: str, headers: dict=DEFAULT_HEADERS, proxies: dict=None): def fetch(url: str, headers: dict=DEFAULT_HEADERS, proxies: dict=None):
try: try:
res = requests.get(url, headers=headers, proxies=proxies) res = requests.get(url, headers=headers, proxies=proxies)
res.raise_for_status() res.raise_for_status()
except Exception as e: except Exception as e:
print(f'[Err] {e}') print(f'[Err] {e}')
else: else:
html = res.text html = res.text
tree = Selector(text=html) tree = Selector(text=html)
return tree return tree

View File

@@ -1,15 +1,15 @@
import unittest import unittest
from flask import url_for from flask import url_for
from rsshub import create_app from rsshub import create_app
class BaseTestCase(unittest.TestCase): class BaseTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
app = create_app('testing') app = create_app('testing')
self.context = app.test_request_context() self.context = app.test_request_context()
self.context.push() self.context.push()
self.client = app.test_client() self.client = app.test_client()
self.runner = app.test_cli_runner() self.runner = app.test_cli_runner()
def tearDown(self): def tearDown(self):
self.context.pop() self.context.pop()

View File

@@ -1,7 +1,7 @@
from tests.base import BaseTestCase from tests.base import BaseTestCase
class CLITestCase(BaseTestCase): class CLITestCase(BaseTestCase):
def test_ptshell(self): def test_ptshell(self):
result = self.runner.invoke(args=['ptshell']) result = self.runner.invoke(args=['ptshell'])
self.assertNotIn('ptpython not installed! Use the default shell instead.', result.output) self.assertNotIn('ptpython not installed! Use the default shell instead.', result.output)

View File

@@ -1,30 +1,30 @@
from flask import current_app, abort from flask import current_app, abort
from tests.base import BaseTestCase from tests.base import BaseTestCase
class ErrorsTestCase(BaseTestCase): class ErrorsTestCase(BaseTestCase):
def test_400(self): def test_400(self):
@current_app.route('/400') @current_app.route('/400')
def bad_request(): def bad_request():
abort(400) abort(400)
response = self.client.get('/400') response = self.client.get('/400')
data = response.get_data(as_text=True) data = response.get_data(as_text=True)
self.assertIn('400 Bad Request', data) self.assertIn('400 Bad Request', data)
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
def test_404(self): def test_404(self):
response = self.client.get('/nothing') response = self.client.get('/nothing')
data = response.get_data(as_text=True) data = response.get_data(as_text=True)
self.assertIn('404 Not Found', data) self.assertIn('404 Not Found', data)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_500(self): def test_500(self):
@current_app.route('/500') @current_app.route('/500')
def internal_server_error_for_test(): def internal_server_error_for_test():
abort(500) abort(500)
response = self.client.get('/500') response = self.client.get('/500')
data = response.get_data(as_text=True) data = response.get_data(as_text=True)
self.assertIn('服务器出错', data) self.assertIn('服务器出错', data)
self.assertEqual(response.status_code, 500) self.assertEqual(response.status_code, 500)

View File

@@ -1,8 +1,8 @@
from flask import url_for from flask import url_for
from tests.base import BaseTestCase from tests.base import BaseTestCase
class MainTestCase(BaseTestCase): class MainTestCase(BaseTestCase):
def test_index(self): def test_index(self):
response = self.client.get(url_for('main.index')) response = self.client.get(url_for('main.index'))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)