├── rssant ├── __init__.py ├── helper │ ├── __init__.py │ └── content_hash.py ├── middleware │ ├── __init__.py │ ├── timer.py │ └── message_storage.py ├── allauth_providers │ ├── __init__.py │ ├── github │ │ ├── __init__.py │ │ ├── urls.py │ │ └── provider.py │ ├── oauth2 │ │ └── __init__.py │ └── helper.py ├── settings │ └── __init__.py ├── tests │ └── email_template_tests.py ├── templates │ └── email │ │ └── recall.html ├── wsgi.py └── auth_serializer.py ├── tests ├── __init__.py ├── common │ ├── __init__.py │ └── test_rss_proxy.py ├── feedlib │ ├── __init__.py │ ├── processor │ │ └── __init__.py │ ├── testdata │ │ ├── parser │ │ │ ├── failed │ │ │ │ ├── jsonfeed-failed-no-title-items.json │ │ │ │ ├── http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml │ │ │ │ ├── https-egoist-moe-atom-xml.xml │ │ │ │ ├── rssant-manifest.json │ │ │ │ ├── http-www-yuming-in-feed.xml │ │ │ │ ├── http-www-ziyouren888-com-feed.xml │ │ │ │ ├── http-porn191-com-index-php-feed.xml │ │ │ │ ├── jsonfeed-failed-syntax.json │ │ │ │ ├── v2ex-jsonfeed-no-storys.json │ │ │ │ ├── http-www-m1927-com-feed-php.xml │ │ │ │ └── http-www-chongdiantou-com-feed.xml │ │ │ ├── warn │ │ │ │ ├── https-tmioe-com-feed.xml │ │ │ │ └── v2ex-jsonfeed-warning.json │ │ │ └── well │ │ │ │ ├── jsonfeed-example.json │ │ │ │ ├── v2ex-jsonfeed.json │ │ │ │ └── v2ex-jsonfeed.xml │ │ ├── encoding │ │ │ ├── chardet │ │ │ │ ├── big5.xml │ │ │ │ ├── euc-jp.xml │ │ │ │ ├── koi8-r.xml │ │ │ │ ├── shift-jis.xml │ │ │ │ ├── tis-620.xml │ │ │ │ ├── euc-kr:cp949.xml │ │ │ │ ├── gb2312:gb18030.xml │ │ │ │ ├── gbk:gb18030.json │ │ │ │ ├── windows-1255.xml │ │ │ │ ├── utf-8.xml │ │ │ │ └── utf-8.json │ │ │ ├── xml │ │ │ │ ├── http_text_xml_charset_overrides_encoding_2.xml │ │ │ │ ├── http_text_xml_default.xml │ │ │ │ ├── http_application_xml_default.xml │ │ │ │ ├── http_text_rss_xml_default.xml │ │ │ │ ├── http_application_rss_xml_default.xml │ │ │ │ ├── http_text_rss_xml_encoding.xml │ │ │ │ ├── http_application_xml_encoding.xml │ │ │ │ ├── http_text_atom_xml_default.xml │ │ │ │ ├── http_application_rss_xml_encoding.xml │ │ │ │ ├── http_application_xml_dtd_default.xml │ │ │ │ ├── http_application_atom_xml_default.xml │ │ │ │ ├── http_text_xml_charset_overrides_encoding.xml │ │ │ │ ├── http_text_atom_xml_encoding.xml │ │ │ │ ├── http_text_xml_epe_default.xml │ │ │ │ ├── http_application_xml_charset_overrides_encoding.xml │ │ │ │ ├── http_text_rss_xml_charset_overrides_encoding.xml │ │ │ │ ├── http_application_xml_dtd_encoding.xml │ │ │ │ ├── http_application_atom_xml_encoding.xml │ │ │ │ ├── http_application_xml_epe_default.xml │ │ │ │ ├── http_text_xml_epe_encoding.xml │ │ │ │ ├── http_application_rss_xml_charset_overrides_encoding.xml │ │ │ │ ├── http_text_atom_xml_charset_overrides_encoding.xml │ │ │ │ ├── http_application_xml_epe_encoding.xml │ │ │ │ ├── http_application_xml_dtd_charset_overrides_encoding.xml │ │ │ │ ├── http_application_atom_xml_charset_overrides_encoding.xml │ │ │ │ ├── http_application_atom_xml_gb2312_encoding.xml │ │ │ │ ├── http_text_xml_epe_charset_overrides_encoding.xml │ │ │ │ ├── http_application_xml_epe_charset_overrides_encoding.xml │ │ │ │ └── http_application_atom_xml_gb2312_charset_overrides_encoding.xml │ │ │ └── mixed │ │ │ │ ├── http_application_rss_xml_charset_overrides_encoding.xml │ │ │ │ └── http_application_atom_xml_charset_overrides_encoding.xml │ │ ├── fulltext │ │ │ ├── hackernews1_rss.html │ │ │ ├── juejin1_rss.html │ │ │ ├── juejin2_rss.html │ │ │ ├── hackernews2_rss.html │ │ │ ├── xkcd_rss.html │ │ │ └── martinfowler_rss.html │ │ ├── feed_type │ │ │ └── html │ │ │ │ ├── http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml │ │ │ │ └── http-www-yuming-in-feed.xml │ │ └── processor │ │ │ ├── html_redirect │ │ │ ├── test_html_redirect_1.html │ │ │ ├── test_html_redirect_3.html │ │ │ └── test_html_redirect_2.html │ │ │ ├── test_iframe.html │ │ │ └── test_iframe_link.html │ ├── test_cacert.py │ ├── test_response.py │ └── test_response_file.py ├── feedserver │ ├── __init__.py │ └── main.py ├── models │ ├── __init__.py │ ├── test_story_key.py │ ├── test_story_unique_ids.py │ └── test_story_data.py ├── test_arxiv_results.json ├── fixtures │ └── screenshots │ │ ├── test_error.png │ │ ├── test_error_20251120082830.png │ │ ├── test_error_20251120082907.png │ │ └── test_error_20251120082943.png ├── test_config.py ├── conftest.py ├── test_changelog.py └── sample │ ├── inoreader.xml │ └── stringer.opml ├── rssant_api ├── __init__.py ├── tests │ ├── __init__.py │ ├── duplicate_feed_detector_tests.py │ ├── story_storage_tests.py │ └── reverse_url_tests.py ├── views │ ├── __init__.py │ ├── common.py │ ├── errors.py │ └── ezrevenue.py ├── migrations │ ├── __init__.py │ ├── 0025_auto_20200516_0959.py │ ├── 0002_auto_20190317_1020.py │ ├── 0015_story_has_mathjax.py │ ├── 0029_userfeed_group.py │ ├── 0023_feed_response_status.py │ ├── 0019_feed_use_proxy.py │ ├── 0030_storyinfo_sentence_count.py │ ├── 0031_story_sentence_count.py │ ├── 0020_feed_checksum_data.py │ ├── 0022_feed_warnings.py │ ├── 0007_userfeed_is_from_bookmark.py │ ├── 0004_rawfeed_is_gzipped.py │ ├── 0028_auto_20200619_0906.py │ ├── 0018_feed_freeze_level.py │ ├── 0056_add_arxiv_report_json_data.py │ ├── 0024_auto_20200510_1008.py │ ├── 0032_auto_20201227_0636.py │ ├── 0016_auto_20191105_1247.py │ ├── 0044_add_citation_fields.py │ ├── 0047_add_ai_entertainment_refs.py │ ├── 0021_auto_20200418_0512.py │ ├── 0051_auto_20251118_1322.py │ ├── 0014_auto_20191027_0558.py │ ├── 0017_auto_20191217_1253.py │ ├── 0006_auto_20190418_0945.py │ ├── 0012_auto_20191025_1526.py │ ├── 0048_add_separate_ai_entertainment_reports.py │ ├── 0009_auto_20190518_0659.py │ ├── 0026_feedstorystat.py │ ├── 0011_auto_20190714_0550.py │ └── 0003_auto_20190327_1304.py ├── models │ ├── story_storage │ │ ├── common │ │ │ └── __init__.py │ │ ├── postgres │ │ │ ├── __init__.py │ │ │ └── postgres_sharding.py │ │ └── __init__.py │ ├── errors.py │ └── registery.py ├── apps.py ├── resources │ └── opml.mako ├── admin.py └── urls.py ├── rssant_cli ├── __init__.py ├── user.py └── scripts │ └── fix_user_story_feed_id.sql ├── scripts ├── __init__.py ├── dev │ ├── quick_check.sh │ ├── rundev-api.sh │ ├── rundev-scheduler.sh │ ├── rundev-worker.sh │ ├── start-all-services.sh │ └── check-services.sh ├── tools │ ├── diagnose_subscription_issue.py │ └── pip-compile.sh ├── run-asyncapi.py ├── run-scheduler.py ├── django_db_init.py ├── setup.sh ├── stop_rsshub.sh ├── postgres_start.sh ├── django_pre_migrate.py ├── migrate_story_v0_1_0.py ├── check_rsshub.sh ├── pg_count.py ├── docker │ └── start-docker.sh └── README_FIX_LOGIN.md ├── constraint.txt ├── rssant_asyncapi ├── __init__.py ├── app.py ├── views.py └── main.py ├── rssant_common ├── __init__.py ├── chnlist │ ├── __init__.py │ ├── chnlist.txt.gz │ └── chnlist.py ├── timezone.py ├── hashid.py ├── base64.py ├── blacklist.py ├── standby_domain.py ├── django_setup.py ├── health.py ├── signature.py ├── requests_helper.py ├── resources │ └── changelog.atom.mako ├── attrdict.py ├── _proxy_helper.py └── throttle.py ├── rssant_harbor └── __init__.py ├── rssant_scheduler ├── __init__.py ├── views.py └── main.py ├── rssant_worker └── __init__.py ├── requirements-build.txt ├── box ├── rssant.env ├── bin │ ├── start-nginx.sh │ ├── start-api.sh │ ├── start-asyncapi.sh │ ├── start-worker.sh │ ├── start-scheduler.sh │ ├── wait-initdb.sh │ ├── start-initdb.sh │ └── start-postgres.sh ├── initdb.sql ├── build-all.sh ├── test.sh ├── setup-container.sh ├── build.sh ├── run.sh ├── push-all.sh └── build-and-restart.sh ├── rssant_config └── __init__.py ├── requirements-pip.txt ├── rssant_feedlib ├── useragent.py ├── dotwhat_data │ ├── other.txt │ └── font.txt ├── cacert │ ├── __main__.py │ ├── __init__.py │ └── cli.py ├── __init__.py └── helper.py ├── .mise.toml ├── unmaintain ├── runlocust.sh ├── benchmark │ ├── benchmark_story_data.py │ └── benchmark_asyncio_postgres.py └── convert_qrcode.py ├── frontend ├── postcss.config.js ├── tsconfig.node.json ├── start-dev.sh ├── src │ ├── main.tsx │ ├── components │ │ ├── ui │ │ │ ├── skeleton.tsx │ │ │ ├── label.tsx │ │ │ ├── textarea.tsx │ │ │ ├── separator.tsx │ │ │ ├── input.tsx │ │ │ ├── toaster.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── switch.tsx │ │ │ ├── badge.tsx │ │ │ └── avatar.tsx │ │ ├── FeedCardSkeleton.tsx │ │ └── PageTransition.tsx │ ├── pages │ │ └── publish │ │ │ └── PublishContext.tsx │ └── store │ │ ├── useServiceClientStore.ts │ │ └── useThemeStore.ts ├── index.html ├── .gitignore ├── tsconfig.json └── dev-update.sh ├── cloudflare_worker └── rssant │ ├── cloudflare-worker.png │ ├── .prettierrc │ ├── wrangler.toml │ ├── .gitignore │ ├── package-lock.json │ └── package.json ├── cursor └── docs │ ├── GBF框架应用说明.md │ ├── 文档索引.md │ ├── 项目概述.md │ ├── 项目结构说明.md │ ├── 外部依赖说明.md │ ├── 文章导出到飞书-技术方案设计.md │ ├── GitHub-技术方案设计.md │ ├── 调度与任务-技术方案设计.md │ ├── HackerNews-技术方案设计.md │ ├── 业务流程说明.md │ ├── RSSHub集成-技术方案设计.md │ ├── ArXiv-技术方案设计.md │ ├── 发布能力-技术方案设计.md │ └── AI娱乐与AIGC-技术方案设计.md ├── .isort.cfg ├── .flake8 ├── .env.backup ├── .travis.yml ├── deploy ├── rssant_server │ ├── build.sh │ ├── deploy-api.sh │ ├── deploy-api-prod.sh │ ├── deploy-worker.sh │ ├── deploy-worker-prod.sh │ └── Dockerfile └── rssant_asyncapi │ ├── build.sh │ ├── pyinstaller_build.sh │ ├── deploy.sh │ ├── deploy-prod.sh │ └── Dockerfile ├── etc ├── resolv.conf └── apt-sources.list ├── requirements-dev.txt ├── pytest.ini ├── .coveragerc ├── prompts ├── github_openai_prompt.txt ├── github_ollama_prompt.txt ├── hacker_news_daily_report_openai_prompt.txt └── hacker_news_hours_topic_openai_prompt.txt ├── .env.example ├── manage.py ├── .pre-commit-config.yaml ├── runserver.py └── cleanup_sensitive_data.sh /rssant/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_cli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /constraint.txt: -------------------------------------------------------------------------------- 1 | cython<3 2 | -------------------------------------------------------------------------------- /rssant/helper/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_api/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_api/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_asyncapi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_harbor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_scheduler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_worker/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/dev/quick_check.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/dev/rundev-api.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/feedlib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/feedserver/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_api/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/dev/rundev-scheduler.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/dev/rundev-worker.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant/allauth_providers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/dev/start-all-services.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/feedlib/processor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-build.txt: -------------------------------------------------------------------------------- 1 | pyinstaller==4.7 2 | -------------------------------------------------------------------------------- /rssant/allauth_providers/github/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant/allauth_providers/oauth2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/tools/diagnose_subscription_issue.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_api/models/story_storage/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rssant_api/models/story_storage/postgres/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /box/rssant.env: -------------------------------------------------------------------------------- 1 | 致命错误:路径 'box/rssant.env' 在磁盘上,但是不在 'HEAD' 中 2 | -------------------------------------------------------------------------------- /rssant_config/__init__.py: -------------------------------------------------------------------------------- 1 | from .env import CONFIG, MAX_FEED_COUNT 2 | -------------------------------------------------------------------------------- /requirements-pip.txt: -------------------------------------------------------------------------------- 1 | pip==20.3.4 2 | wheel==0.36.2 3 | setuptools==51.3.3 4 | -------------------------------------------------------------------------------- /rssant/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .settings import * # noqa: F401,F403 2 | -------------------------------------------------------------------------------- /rssant_common/chnlist/__init__.py: -------------------------------------------------------------------------------- 1 | from .chnlist import CHINA_WEBSITE_LIST 2 | -------------------------------------------------------------------------------- /rssant_feedlib/useragent.py: -------------------------------------------------------------------------------- 1 | from rssant_common.useragent import * # noqa 2 | -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | python = "3.8.12" 3 | 4 | [env] 5 | _.python.venv = ".venv" 6 | -------------------------------------------------------------------------------- /rssant_feedlib/dotwhat_data/other.txt: -------------------------------------------------------------------------------- 1 | .css - css 2 | .js - javascript 3 | .ico - favicon 4 | -------------------------------------------------------------------------------- /box/bin/start-nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | /usr/sbin/nginx -g 'daemon off;' 6 | -------------------------------------------------------------------------------- /rssant_feedlib/cacert/__main__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | if __name__ == "__main__": 4 | cli() 5 | -------------------------------------------------------------------------------- /unmaintain/runlocust.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | locust -f unmaintain/locustfile.py --host 'http://127.0.0.1:6788' 3 | -------------------------------------------------------------------------------- /box/initdb.sql: -------------------------------------------------------------------------------- 1 | CREATE ROLE rssant LOGIN SUPERUSER PASSWORD 'rssant'; 2 | CREATE DATABASE rssant OWNER = rssant; 3 | -------------------------------------------------------------------------------- /tests/test_arxiv_results.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": 11, 3 | "passed": 11, 4 | "failed": 0, 5 | "errors": [] 6 | } 7 | -------------------------------------------------------------------------------- /rssant_feedlib/cacert/__init__.py: -------------------------------------------------------------------------------- 1 | from .cacert import CacertHelper as _CacertHelper 2 | 3 | where = _CacertHelper().where 4 | -------------------------------------------------------------------------------- /rssant_api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RssantApiConfig(AppConfig): 5 | name = 'rssant_api' 6 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | 8 | -------------------------------------------------------------------------------- /rssant_common/chnlist/chnlist.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/rssant_common/chnlist/chnlist.txt.gz -------------------------------------------------------------------------------- /scripts/run-asyncapi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from rssant_asyncapi.main import main 3 | 4 | if __name__ == '__main__': 5 | main() 6 | -------------------------------------------------------------------------------- /scripts/run-scheduler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from rssant_scheduler.main import main 3 | 4 | if __name__ == '__main__': 5 | main() 6 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/jsonfeed-failed-no-title-items.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "https://jsonfeed.org/version/1", 3 | "items": [] 4 | } -------------------------------------------------------------------------------- /tests/fixtures/screenshots/test_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/fixtures/screenshots/test_error.png -------------------------------------------------------------------------------- /cloudflare_worker/rssant/cloudflare-worker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/cloudflare_worker/rssant/cloudflare-worker.png -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/big5.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/big5.xml -------------------------------------------------------------------------------- /cursor/docs/GBF框架应用说明.md: -------------------------------------------------------------------------------- 1 | # GBF框架应用说明 2 | 3 | - 项目未使用 GBF 框架。 4 | - 架构类型为 Django + DRF + 自研调度与多角色运行模型,领域模型以 Django ORM 为核心,聚合模型 `UnionFeed/UnionStory` 为用户视角的数据访问层。 -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/euc-jp.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/euc-jp.xml -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/koi8-r.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/koi8-r.xml -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | multi_line_output=VERTICAL_HANGING_INDENT 3 | include_trailing_comma=true 4 | sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 5 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/shift-jis.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/shift-jis.xml -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/tis-620.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/tis-620.xml -------------------------------------------------------------------------------- /cloudflare_worker/rssant/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "all", 5 | "tabWidth": 2, 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /cloudflare_worker/rssant/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "rssant" 2 | account_id = "8772320df1662c1375bb57a344a1f78e" 3 | workers_dev = true 4 | compatibility_date = "2024-01-08" 5 | -------------------------------------------------------------------------------- /cloudflare_worker/rssant/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /dist 3 | **/*.rs.bk 4 | Cargo.lock 5 | bin/ 6 | pkg/ 7 | wasm-pack.log 8 | worker/ 9 | node_modules/ 10 | .cargo-ok 11 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/euc-kr:cp949.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/euc-kr:cp949.xml -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/gb2312:gb18030.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/gb2312:gb18030.xml -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/gbk:gb18030.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/gbk:gb18030.json -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/windows-1255.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/chardet/windows-1255.xml -------------------------------------------------------------------------------- /tests/fixtures/screenshots/test_error_20251120082830.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/fixtures/screenshots/test_error_20251120082830.png -------------------------------------------------------------------------------- /tests/fixtures/screenshots/test_error_20251120082907.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/fixtures/screenshots/test_error_20251120082907.png -------------------------------------------------------------------------------- /tests/fixtures/screenshots/test_error_20251120082943.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/fixtures/screenshots/test_error_20251120082943.png -------------------------------------------------------------------------------- /tests/feedlib/testdata/fulltext/hackernews1_rss.html: -------------------------------------------------------------------------------- 1 | Comments on Hackernews: https://news.ycombinator.com/item?id=25684838 -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/warn/https-tmioe-com-feed.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/parser/warn/https-tmioe-com-feed.xml -------------------------------------------------------------------------------- /tests/feedlib/testdata/fulltext/juejin1_rss.html: -------------------------------------------------------------------------------- 1 | Flutter是一个优秀的UI框架,借助它开箱即用的Widgets我们能够构建出漂亮和高性能的用户界面。那这些Widgets到底是如何工作的又是如何完成渲染的。 在本文中呢,我们就来探析Widgets背后的故事-Flutter渲染机制之三棵树。 在Flutter中和Widget… -------------------------------------------------------------------------------- /tests/feedlib/testdata/fulltext/juejin2_rss.html: -------------------------------------------------------------------------------- 1 | 上篇文章简单介绍了以下webpack内部WDS的使用,这节讨论一下WDS内部proxy的配置方式。 webpack-devServer,一般简称WDS,是 webpack 内置的用于开发环境的服务器配置。webpack本身提供三种方式用于开发环境修改代码以后自动编译,以提高开发… 2 | -------------------------------------------------------------------------------- /box/bin/start-api.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | /app/box/bin/wait-initdb.sh 6 | 7 | export RSSANT_ROLE=api 8 | export RSSANT_BIND_ADDRESS=0.0.0.0:6788 9 | /app/runserver.py 10 | -------------------------------------------------------------------------------- /box/bin/start-asyncapi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | /app/box/bin/wait-initdb.sh 6 | 7 | export RSSANT_ROLE=asyncapi 8 | export RSSANT_BIND_ADDRESS=0.0.0.0:6786 9 | /app/runserver.py 10 | -------------------------------------------------------------------------------- /box/bin/start-worker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | /app/box/bin/wait-initdb.sh 6 | 7 | export RSSANT_ROLE=worker 8 | export RSSANT_BIND_ADDRESS=0.0.0.0:6793 9 | /app/runserver.py 10 | -------------------------------------------------------------------------------- /box/bin/start-scheduler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | /app/box/bin/wait-initdb.sh 6 | 7 | export RSSANT_ROLE=scheduler 8 | export RSSANT_BIND_ADDRESS=0.0.0.0:6790 9 | /app/runserver.py 10 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/fulltext/hackernews2_rss.html: -------------------------------------------------------------------------------- 1 |

Comments: https://news.ycombinator.com/item?id=25730145

2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore= 3 | E203 E251 W503 E704 4 | per-file-ignores= 5 | */__init__.py:F401 6 | max-line-length=120 7 | exclude= 8 | rssant_api/migrations/ 9 | static/ 10 | data/ 11 | -------------------------------------------------------------------------------- /scripts/django_db_init.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from rssant_harbor.django_service import django_run_db_init 4 | 5 | LOG = logging.getLogger(__name__) 6 | 7 | 8 | def run(): 9 | django_run_db_init() 10 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding_2.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding_2.xml -------------------------------------------------------------------------------- /scripts/tools/pip-compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | export PIP_CONSTRAINT="constraint.txt" 6 | pip-compile \ 7 | --no-emit-index-url \ 8 | --output-file requirements.txt \ 9 | requirements.in 10 | -------------------------------------------------------------------------------- /rssant/allauth_providers/github/urls.py: -------------------------------------------------------------------------------- 1 | from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns 2 | 3 | from .provider import RssantGitHubProvider 4 | 5 | urlpatterns = default_urlpatterns(RssantGitHubProvider) 6 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/feed_type/html/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/feed_type/html/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanGongwanxiaowan/rss-aigc/HEAD/tests/feedlib/testdata/parser/failed/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_xml_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /rssant/allauth_providers/github/provider.py: -------------------------------------------------------------------------------- 1 | from allauth.socialaccount.providers.github.provider import GitHubProvider 2 | 3 | 4 | class RssantGitHubProvider(GitHubProvider): 5 | """RssantGitHubProvider""" 6 | 7 | 8 | provider_classes = [RssantGitHubProvider] 9 | -------------------------------------------------------------------------------- /rssant_api/models/story_storage/__init__.py: -------------------------------------------------------------------------------- 1 | from .common.story_data import StoryData 2 | from .common.story_key import StoryId, StoryKey, hash_feed_id 3 | from .postgres.postgres_story import PostgresStoryStorage 4 | from .postgres.postgres_client import PostgresClient 5 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_rss_xml_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | 12 | -------------------------------------------------------------------------------- /rssant_api/models/story_storage/postgres/postgres_sharding.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | VOLUME_SIZE = 64 * 1024 4 | 5 | 6 | def sharding_for(feed_id: int) -> int: 7 | """ 8 | 数据分片算法,按 FeedID 范围分片。 9 | 每卷存储 64K 订阅的故事数据,大约64GB,1千万行记录。 10 | """ 11 | return feed_id // VOLUME_SIZE 12 | -------------------------------------------------------------------------------- /box/build-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | VERSION=$1 6 | if [ -z "$VERSION" ]; then 7 | VERSION='latest' 8 | fi 9 | echo "*** Build guyskk/rssant:$VERSION ***" 10 | 11 | bash box/build.sh \ 12 | --platform linux/amd64,linux/arm64 \ 13 | -t "guyskk/rssant:$VERSION" 14 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/utf-8.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 《11月的蕭邦》是台灣歌手周杰倫發行第六張國語專輯 8 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_rss_xml_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/start-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 启动前端开发服务器脚本 3 | # 确保使用正确的 Node.js 版本 4 | 5 | cd "$(dirname "$0")" 6 | 7 | # 加载 nvm 8 | export NVM_DIR="$HOME/.nvm" 9 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 10 | 11 | # 使用 Node.js 18 12 | nvm use 18 13 | 14 | # 启动开发服务器 15 | npm run dev 16 | -------------------------------------------------------------------------------- /rssant_cli/user.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import click 4 | 5 | import rssant_common.django_setup # noqa:F401 6 | 7 | LOG = logging.getLogger(__name__) 8 | 9 | 10 | @click.group() 11 | def main(): 12 | """User Commands""" 13 | 14 | 15 | if __name__ == "__main__": 16 | main() 17 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_rss_xml_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /.env.backup: -------------------------------------------------------------------------------- 1 | # RSS订阅服务配置文件 2 | 3 | # AI分析功能配置(智谱AI) 4 | # AI模型配置:格式 model_id,model_name 5 | RSSANT_AI_MODEL_CONFIG=glm-z1-flash,glm-z1-flash 6 | 7 | # AI API基础URL(智谱AI) 8 | RSSANT_AI_API_BASE_URL=https://open.bigmodel.cn/api/paas/v4 9 | 10 | # AI API密钥(智谱AI) 11 | RSSANT_AI_API_KEY=YOUR_AI_API_KEY 12 | -------------------------------------------------------------------------------- /rssant_api/resources/opml.mako: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Feeds from RSS-AIGC 5 | 6 | 7 | % for feed in feeds: 8 | 9 | % endfor 10 | 11 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | pip install -r requirements-pip.txt 6 | PIP_CONSTRAINT=constraint.txt pip install \ 7 | -r requirements.txt \ 8 | -r requirements-dev.txt \ 9 | -r requirements-build.txt 10 | 11 | pre-commit install 12 | pre-commit run --all-files 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: xenial 3 | language: shell 4 | services: 5 | - docker 6 | install: 7 | - bash ./box/build.sh \ 8 | --build-arg PYPI_MIRROR=https://pypi.org/simple/ \ 9 | --build-arg NPM_REGISTERY="--registry=https://registry.npmjs.org" 10 | script: 11 | - bash ./box/test.sh 12 | -------------------------------------------------------------------------------- /deploy/rssant_server/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas build \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-server \ 8 | --dockerfile deploy/rssant_server/Dockerfile \ 9 | --build-platform linux/amd64 \ 10 | $@ 11 | -------------------------------------------------------------------------------- /deploy/rssant_asyncapi/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas build \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-asyncapi \ 8 | --dockerfile deploy/rssant_asyncapi/Dockerfile \ 9 | --build-platform linux/amd64 \ 10 | $@ 11 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_atom_xml_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedserver/main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, redirect 2 | 3 | 4 | app = Flask(__name__) 5 | 6 | 7 | @app.route('/feed/302') 8 | def do_redirect(): 9 | target = 'http://blog.guyskk.com/feed.xml' 10 | return redirect(target, 302) 11 | 12 | 13 | if __name__ == "__main__": 14 | app.run() 15 | -------------------------------------------------------------------------------- /etc/resolv.conf: -------------------------------------------------------------------------------- 1 | nameserver 223.5.5.5 2 | nameserver 223.6.6.6 3 | nameserver 114.114.114.114 4 | nameserver 114.114.115.115 5 | nameserver 180.76.76.76 6 | nameserver 119.29.29.29 7 | nameserver 8.8.8.8 8 | nameserver 8.8.4.4 9 | nameserver 2400:3200::1 10 | nameserver 2400:3200:baba::1 11 | nameserver 2400:da00::6666 -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_rss_xml_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /cursor/docs/文档索引.md: -------------------------------------------------------------------------------- 1 | # 文档索引 2 | 3 | - 项目概述:`cursor/docs/项目概述.md` 4 | - 领域模型说明:`cursor/docs/领域模型说明.md` 5 | - 接口文档:`cursor/docs/接口文档.md` 6 | - 业务流程说明:`cursor/docs/业务流程说明.md` 7 | - 项目结构说明:`cursor/docs/项目结构说明.md` 8 | - 外部依赖说明:`cursor/docs/外部依赖说明.md` 9 | - GBF框架应用说明:`cursor/docs/GBF框架应用说明.md` 10 | - 项目梳理文档:`cursor/docs/项目梳理文档.md` -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_atom_xml_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /deploy/rssant_asyncapi/pyinstaller_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pyinstaller ../scripts/run-asyncapi.py \ 4 | --exclude-module django \ 5 | --exclude-module lxml \ 6 | --exclude-module gevent \ 7 | --hidden-import gunicorn.glogging \ 8 | --collect-data rssant_common \ 9 | -d noarchive \ 10 | --noconfirm 11 | -------------------------------------------------------------------------------- /deploy/rssant_asyncapi/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas deploy-aliyun \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-asyncapi \ 8 | --dockerfile deploy/rssant_asyncapi/Dockerfile \ 9 | --function rssant-asyncapi-qa \ 10 | --build-id \ 11 | $@ 12 | -------------------------------------------------------------------------------- /tests/feedlib/test_cacert.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from rssant_feedlib import cacert 3 | from rssant_feedlib.cacert import _CacertHelper 4 | 5 | 6 | def test_cacert(): 7 | assert cacert.where() 8 | 9 | 10 | @pytest.mark.xfail(run=False, reason='depends on test network') 11 | def test_cacert_update(): 12 | _CacertHelper.update() 13 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_atom_xml_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /deploy/rssant_asyncapi/deploy-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas deploy-aliyun \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-asyncapi \ 8 | --dockerfile deploy/rssant_asyncapi/Dockerfile \ 9 | --function rssant-asyncapi \ 10 | --build-id \ 11 | $@ 12 | -------------------------------------------------------------------------------- /rssant_common/timezone.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime, time, timedelta, timezone # noqa: F401 2 | 3 | UTC = timezone.utc 4 | CST = timezone(timedelta(hours=8), name='Asia/Shanghai') 5 | 6 | 7 | def now() -> datetime: 8 | """ 9 | >>> now().tzinfo == UTC 10 | True 11 | """ 12 | return datetime.now(timezone.utc) 13 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_xml_epe_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /box/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | bash ./box/run.sh 6 | sleep 3 7 | docker ps --latest 8 | docker logs --tail 1000 rssant 9 | 10 | docker run -ti guyskk/rssant:latest pytest -m 'not dbtest' 11 | 12 | docker exec -ti rssant bash box/bin/wait-initdb.sh 13 | docker exec -ti rssant pytest -m 'dbtest' 14 | docker rm -f rssant || true 15 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | import 'highlight.js/styles/github.css' 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | , 11 | ) 12 | 13 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_rss_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /box/bin/wait-initdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wait_file() { 4 | local file="$1"; shift 5 | # 180 seconds as default timeout 6 | local wait_seconds="${1:-180}"; shift 7 | until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done 8 | ((++wait_seconds)) 9 | } 10 | 11 | wait_file "/app/data/initdb.ready" 12 | echo initdb ready! 13 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/processor/html_redirect/test_html_redirect_1.html: -------------------------------------------------------------------------------- 1 | https://blog.example.com/html-redirect/ -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_atom_xml_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_epe_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_xml_epe_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | 17 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_rss_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/fulltext/xkcd_rss.html: -------------------------------------------------------------------------------- 1 | Neutral Evil is for people who like keeping the weight nicely centered in the carton, but also hate everyone else who wants that. -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/https-egoist-moe-atom-xml.xml: -------------------------------------------------------------------------------- 1 | EGOIST v1-legacy 2 | 3 |

See ya! I no longer publish stuff here (I've never published any meaningful stuff anyways), subscribe my Telegram Channel instead.

4 | 5 |

感觉写不出什么有意义的文章,所以此博客已被我的 Telegram 频道 取代。

6 | -------------------------------------------------------------------------------- /rssant_common/hashid.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> k = HASH_ID.encode(123) 3 | >>> len(k) 4 | 6 5 | >>> HASH_ID.decode(k)[0] 6 | 123 7 | """ 8 | from hashids import Hashids 9 | from rssant_config import CONFIG 10 | from .unionid import UNION_ID_CHARS 11 | 12 | HASH_ID = Hashids( 13 | salt=CONFIG.hashid_salt, 14 | min_length=6, 15 | alphabet=UNION_ID_CHARS.decode('utf-8'), 16 | ) 17 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/processor/test_iframe.html: -------------------------------------------------------------------------------- 1 | 《怪物猎人 世界:冰原世纪》x《生化危机2 重制版》联动任务


-------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_atom_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/rssant-manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"RSS-AIGC","short_name":"RSS-AIGC","theme_color":"#f9f9f9","icons":[{"src":"/img/icons/android-chrome-192x192.png?v=2020032001","sizes":"192x192","type":"image/png"},{"src":"/img/icons/android-chrome-512x512.png?v=2020032001","sizes":"512x512","type":"image/png"}],"start_url":"/","display":"standalone","background_color":"#ffffff"} -------------------------------------------------------------------------------- /deploy/rssant_server/deploy-api.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas deploy-aliyun \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-server \ 8 | --dockerfile deploy/rssant_server/Dockerfile \ 9 | --function rssant-api-qa \ 10 | --envfile "$RSSANT_ENV_DIR/rssant-api-qa.env" \ 11 | --build-id \ 12 | $@ 13 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_epe_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | 测试配置文件 5 | 包含所有测试共用的常量配置 6 | """ 7 | import os 8 | 9 | # 测试环境配置 10 | FRONTEND_URL = os.getenv('FRONTEND_URL', 'http://localhost:5173') 11 | WEBHOOK_URL = os.getenv('WEBHOOK_URL', '') 12 | TEST_USERNAME = os.getenv('TEST_USERNAME', 'admin') 13 | TEST_PASSWORD = os.getenv('TEST_PASSWORD', 'admin') 14 | 15 | -------------------------------------------------------------------------------- /deploy/rssant_server/deploy-api-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas deploy-aliyun \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-server \ 8 | --dockerfile deploy/rssant_server/Dockerfile \ 9 | --function rssant-api \ 10 | --envfile "$RSSANT_ENV_DIR/rssant-api-prod.env" \ 11 | --build-id \ 12 | $@ 13 | -------------------------------------------------------------------------------- /deploy/rssant_server/deploy-worker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas deploy-aliyun \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-server \ 8 | --dockerfile deploy/rssant_server/Dockerfile \ 9 | --function rssant-worker-qa \ 10 | --envfile "$RSSANT_ENV_DIR/rssant-worker-qa.env" \ 11 | --build-id \ 12 | $@ 13 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # django 2 | pytest-django==3.10.0 3 | 4 | # test 5 | pytest==5.4.1 6 | pytest-cov==2.8.1 7 | pytest-httpserver==0.3.4 8 | flake8==3.7.9 9 | pycodestyle==2.5.0 10 | isort==5.10.1 11 | coverage==4.5.4 12 | flask==2.0.2 13 | autopep8==1.5 14 | pre-commit==2.3.0 15 | locust==1.4.1 16 | geventhttpclient==1.5.3 17 | 18 | # package 19 | pip-tools==5.5.0 20 | pipdeptree==0.13.2 21 | -------------------------------------------------------------------------------- /deploy/rssant_server/deploy-worker-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # shellcheck disable=SC2068 6 | ezfaas deploy-aliyun \ 7 | --repository registry.cn-zhangjiakou.aliyuncs.com/rssant/rssant-server \ 8 | --dockerfile deploy/rssant_server/Dockerfile \ 9 | --function rssant-worker \ 10 | --envfile "$RSSANT_ENV_DIR/rssant-worker-prod.env" \ 11 | --build-id \ 12 | $@ 13 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/http-www-yuming-in-feed.xml: -------------------------------------------------------------------------------- 1 |





如果您的页面没有自动跳转,请点击这里

-------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs=data dist build static .vscode .venv 3 | addopts=--doctest-modules -v --cov . -W all --ignore=data --ignore=box --ignore=unmaintain --ignore=build --ignore=dist --ignore=.venv 4 | doctest_encoding=UTF-8 5 | python_files=test_*.py *_test.py tests.py *_tests.py 6 | DJANGO_SETTINGS_MODULE=rssant.settings 7 | markers= 8 | dbtest: mark a test which need access django database. 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_atom_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/feed_type/html/http-www-yuming-in-feed.xml: -------------------------------------------------------------------------------- 1 |





如果您的页面没有自动跳转,请点击这里

-------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/http-www-ziyouren888-com-feed.xml: -------------------------------------------------------------------------------- 1 |





如果您的页面没有自动跳转,请点击这里

-------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | import rssant_common.django_setup # noqa:F401 4 | 5 | from .rss_proxy_server import * # noqa:F401,F403 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def event_loop(): 10 | # Fix pytest-asyncio ResourceWarning: unclosed socket 11 | loop = asyncio.get_event_loop() 12 | try: 13 | yield loop 14 | finally: 15 | loop.close() 16 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/mixed/http_application_rss_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 《11月的蕭邦》是台灣歌手周杰倫發行第六張國語專輯 9 | 10 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_text_xml_epe_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/processor/html_redirect/test_html_redirect_3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Tudors 4 | 5 | 6 | 7 |

This page has moved to a 8 | theTudors.example.com.

9 | 10 | -------------------------------------------------------------------------------- /rssant_feedlib/__init__.py: -------------------------------------------------------------------------------- 1 | from .parser import FeedParser, FeedResult 2 | from .raw_parser import RawFeedParser, RawFeedResult, FeedParserError 3 | from .feed_checksum import FeedChecksum 4 | from .finder import FeedFinder 5 | from .reader import FeedReader 6 | from .async_reader import AsyncFeedReader 7 | from .response import FeedResponse, FeedContentType, FeedResponseStatus 8 | from .response_builder import FeedResponseBuilder 9 | -------------------------------------------------------------------------------- /rssant_asyncapi/app.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web 2 | 3 | from rssant_config import CONFIG 4 | from rssant_common.logger import configure_logging 5 | 6 | from .views import routes 7 | 8 | 9 | def create_app(): 10 | configure_logging(level=CONFIG.log_level) 11 | api = web.Application() 12 | api.router.add_routes(routes) 13 | app = web.Application() 14 | app.add_subapp('/api/v1', api) 15 | return app 16 | -------------------------------------------------------------------------------- /rssant_feedlib/cacert/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | from rssant_common.logger import configure_logging 3 | from .cacert import CacertHelper 4 | 5 | 6 | @click.group() 7 | def cli(): 8 | """cacert commands""" 9 | 10 | 11 | @click.option('--debug', is_flag=True) 12 | @cli.command() 13 | def update(debug): 14 | """update cacert""" 15 | if debug: 16 | configure_logging(level='DEBUG') 17 | CacertHelper.update() 18 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | .venv/* 4 | */migrations/* 5 | */tests/* 6 | */test_*.py 7 | manage.py 8 | */settings/* 9 | */__init__.py 10 | source = . 11 | branch = True 12 | 13 | [report] 14 | exclude_lines = 15 | pragma: no cover 16 | def __repr__ 17 | raise AssertionError 18 | raise NotImplementedError 19 | if __name__ == .__main__.: 20 | if TYPE_CHECKING: 21 | @abstractmethod -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | AIGC商业新闻平台 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_xml_epe_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /rssant_common/base64.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | 4 | class UrlsafeBase64: 5 | 6 | @classmethod 7 | def encode(cls, data: bytes) -> str: 8 | if not data: 9 | return '' 10 | return base64.urlsafe_b64encode(data).decode('ascii') 11 | 12 | @classmethod 13 | def decode(cls, data: str) -> bytes: 14 | if not data: 15 | return b'' 16 | return base64.urlsafe_b64decode(data) 17 | -------------------------------------------------------------------------------- /frontend/src/pages/publish/PublishContext.tsx: -------------------------------------------------------------------------------- 1 | import { Feed, UserPublish } from '@/lib/api' 2 | import { useOutletContext } from 'react-router-dom' 3 | 4 | export interface PublishOutletContext { 5 | unionId: string 6 | info: UserPublish | null 7 | feeds: Feed[] 8 | reload: () => Promise 9 | loading: boolean 10 | error?: string | null 11 | } 12 | 13 | export const usePublishContext = () => useOutletContext() 14 | 15 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/mixed/http_application_atom_xml_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 《11月的蕭邦》是台灣歌手周杰倫發行第六張國語專輯 9 | 10 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_charset_overrides_encoding.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/processor/html_redirect/test_html_redirect_2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Tudors 4 | 5 | 6 | 7 |

This page has moved to a 8 | theTudors.example.com.

9 | 10 | -------------------------------------------------------------------------------- /cloudflare_worker/rssant/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rssant", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "prettier": { 8 | "version": "1.19.1", 9 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", 10 | "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rssant_api/migrations/0025_auto_20200516_0959.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-05-16 09:59 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0024_auto_20200510_1008'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveIndex( 14 | model_name='story', 15 | name='rssant_api__feed_id_1bed8a_idx', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /cloudflare_worker/rssant/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "rssant", 4 | "version": "1.0.0", 5 | "description": "RSSAnt Proxy", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "format": "prettier --write '**/*.{js,css,json,md}'" 10 | }, 11 | "author": "guyskk ", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "prettier": "^1.18.2" 15 | }, 16 | "dependencies": {} 17 | } 18 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Environment variables 27 | .env 28 | .env.local 29 | .env.production.local 30 | .env.development.local 31 | 32 | -------------------------------------------------------------------------------- /prompts/github_openai_prompt.txt: -------------------------------------------------------------------------------- 1 | 你接下来收到的都是开源项目的最新进展。 2 | 3 | 你根据进展,总结成一个中文的报告,以 项目名称和日期 开头,包含:新增功能、主要改进,修复问题等章节。 4 | 5 | 参考示例如下: 6 | 7 | # LangChain 项目进展 8 | 9 | ## 时间周期:2024-08-13至2024-08-18 10 | 11 | ## 新增功能 12 | - langchain-box: 添加langchain box包和DocumentLoader 13 | - 添加嵌入集成测试 14 | 15 | ## 主要改进 16 | - 将@root_validator用法升级以与pydantic 2保持一致 17 | - 将根验证器升级为与pydantic 2兼容 18 | 19 | ## 修复问题 20 | - 修复Azure的json模式问题 21 | - 修复Databricks Vector Search演示笔记本问题 22 | - 修复Microsoft Azure Cosmos集成测试中的连接字符串问题 23 | 24 | -------------------------------------------------------------------------------- /rssant_api/migrations/0002_auto_20190317_1020.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-17 10:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='feed', 15 | name='dt_updated', 16 | field=models.DateTimeField(help_text='更新时间'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_cli/scripts/fix_user_story_feed_id.sql: -------------------------------------------------------------------------------- 1 | --- 修复feed merge导致user story数据不一致的问题 2 | WITH target AS ( 3 | SELECT us.id AS id, us.feed_id AS old_feed_id, uf.feed_id AS new_feed_id 4 | FROM rssant_api_userstory AS us 5 | JOIN rssant_api_userfeed AS uf ON us.user_feed_id = uf.id 6 | WHERE us.feed_id != uf.feed_id 7 | ) 8 | UPDATE rssant_api_userstory AS us 9 | SET feed_id=target.new_feed_id 10 | FROM target WHERE us.id=target.id 11 | RETURNING target.id, us.user_id, target.old_feed_id, target.new_feed_id; 12 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/well/jsonfeed-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "https://jsonfeed.org/version/1", 3 | "title": "My Example Feed", 4 | "home_page_url": "https://example.org/", 5 | "feed_url": "https://example.org/feed.json", 6 | "items": [ 7 | { 8 | "id": "2", 9 | "content_text": "This is a second item.", 10 | "url": "https://example.org/second-item" 11 | }, 12 | { 13 | "id": "1", 14 | "content_html": "

Hello, world!

", 15 | "url": "https://example.org/initial-post" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /prompts/github_ollama_prompt.txt: -------------------------------------------------------------------------------- 1 | 你是一个热爱开源社区的技术爱好者,经常关注 GitHub 上热门开源项目的进展。 2 | 3 | 任务: 4 | 1.你收到的开源项目 Closed issues 分类整理为:新增功能、主要改进,修复问题等。 5 | 2.将1中的整理结果生成一个中文报告,符合以下的参考格式 6 | 7 | 格式: 8 | # {repo} 项目进展 9 | 10 | ## 时间周期:{date} 11 | 12 | ## 新增功能 13 | - langchain-box: 添加langchain box包和DocumentLoader 14 | - 添加嵌入集成测试 15 | 16 | ## 主要改进 17 | - 将@root_validator用法升级以与pydantic 2保持一致 18 | - 将根验证器升级为与pydantic 2兼容 19 | 20 | ## 修复问题 21 | - 修复Azure的json模式问题 22 | - 修复Databricks Vector Search演示笔记本问题 23 | - 修复Microsoft Azure Cosmos集成测试中的连接字符串问题 24 | 25 | -------------------------------------------------------------------------------- /rssant_api/migrations/0015_story_has_mathjax.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.6 on 2019-10-30 15:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0014_auto_20191027_0558'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='story', 15 | name='has_mathjax', 16 | field=models.BooleanField(blank=True, default=False, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /box/bin/start-initdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | rm -f /app/data/initdb.ready 6 | 7 | # wait postgres ready 8 | su - postgres -c 'while true; do /usr/bin/pg_isready; status=$?; if [[ $status -eq 0 ]]; then break; fi; sleep 1; done;' 9 | 10 | # execute initdb.sql 11 | su - postgres -c 'psql -f /app/box/initdb.sql' 12 | 13 | # django migrate & initdb 14 | python manage.py runscript django_pre_migrate 15 | python manage.py migrate 16 | python manage.py runscript django_db_init 17 | 18 | touch /app/data/initdb.ready 19 | exit 0 20 | -------------------------------------------------------------------------------- /rssant/tests/email_template_tests.py: -------------------------------------------------------------------------------- 1 | from rssant.email_template import EMAIL_CONFIRM_TEMPLATE 2 | from rssant_config import CONFIG 3 | 4 | 5 | def test_render_email_confirm(): 6 | ctx = { 7 | "link": 'https://rss.anyant.com/', 8 | "username": 'guyskk@localhost.com', 9 | 'rssant_url': CONFIG.root_url, 10 | 'rssant_email': CONFIG.smtp_username, 11 | } 12 | html = EMAIL_CONFIRM_TEMPLATE.render_html(**ctx) 13 | for key, value in ctx.items(): 14 | assert value in html, f'{key} {value!r} not rendered' 15 | -------------------------------------------------------------------------------- /rssant_api/migrations/0029_userfeed_group.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-01 11:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0028_auto_20200619_0906'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='userfeed', 15 | name='group', 16 | field=models.CharField(blank=True, help_text='用户设置的分组', max_length=200, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_api/migrations/0023_feed_response_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-04-28 12:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0022_feed_warnings'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feed', 15 | name='response_status', 16 | field=models.IntegerField(blank=True, help_text='response status code', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /scripts/stop_rsshub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # RSSHub 停止脚本 4 | 5 | set -e 6 | 7 | # 获取脚本所在目录 8 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 9 | cd "$SCRIPT_DIR/.." 10 | 11 | # 使用 docker compose 或 docker-compose 12 | if docker compose version &> /dev/null; then 13 | DOCKER_COMPOSE="docker compose" 14 | else 15 | DOCKER_COMPOSE="docker-compose" 16 | fi 17 | 18 | # 配置文件路径 19 | COMPOSE_FILE="docker-compose.rsshub.yml" 20 | 21 | echo "停止 RSSHub 服务..." 22 | $DOCKER_COMPOSE -f "$COMPOSE_FILE" down 23 | 24 | echo "✓ RSSHub 服务已停止" 25 | 26 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/encoding/chardet/utf-8.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "https://jsonfeed.org/version/1", 3 | "title": "My Example Feed", 4 | "home_page_url": "https://example.org/", 5 | "feed_url": "https://example.org/feed.json", 6 | "items": [ 7 | { 8 | "id": "2", 9 | "content_text": "This is a second item.", 10 | "url": "https://example.org/second-item" 11 | }, 12 | { 13 | "id": "1", 14 | "content_html": "

Hello, world!

", 15 | "url": "https://example.org/initial-post" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /rssant_api/migrations/0019_feed_use_proxy.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-04-02 14:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0018_feed_freeze_level'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feed', 15 | name='use_proxy', 16 | field=models.BooleanField(blank=True, default=False, help_text='use proxy or not', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_api/migrations/0030_storyinfo_sentence_count.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-22 06:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0029_userfeed_group'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='storyinfo', 15 | name='sentence_count', 16 | field=models.IntegerField(blank=True, help_text='sentence count', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_api/migrations/0031_story_sentence_count.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-22 11:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0030_storyinfo_sentence_count'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='story', 15 | name='sentence_count', 16 | field=models.IntegerField(blank=True, help_text='sentence count', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_api/migrations/0020_feed_checksum_data.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-04-16 11:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0019_feed_use_proxy'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feed', 15 | name='checksum_data', 16 | field=models.BinaryField(blank=True, help_text='feed checksum data', max_length=4096, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_api/migrations/0022_feed_warnings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-04-18 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0021_auto_20200418_0512'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feed', 15 | name='warnings', 16 | field=models.TextField(blank=True, help_text='warning messages when processing the feed', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_api/migrations/0007_userfeed_is_from_bookmark.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-19 14:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0006_auto_20190418_0945'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='userfeed', 15 | name='is_from_bookmark', 16 | field=models.BooleanField(blank=True, default=False, help_text='是否从书签导入', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rssant_api/views/common.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | from rest_framework.permissions import BasePermission 4 | 5 | from rssant_config import CONFIG 6 | 7 | 8 | class AllowServiceClient(BasePermission): 9 | def has_permission(self, request, view): 10 | secret = request.META.get('HTTP_X_RSSANT_SERVICE_SECRET') or '' 11 | expected_secret = CONFIG.service_secret or '' 12 | return secrets.compare_digest(secret, expected_secret) 13 | 14 | def has_object_permission(self, request, view, obj): 15 | return self.has_permission(request, view) 16 | -------------------------------------------------------------------------------- /rssant_api/migrations/0004_rawfeed_is_gzipped.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-27 13:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0003_auto_20190327_1304'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='rawfeed', 15 | name='is_gzipped', 16 | field=models.BooleanField(blank=True, default=False, help_text='is content gzip compressed', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /box/setup-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mkdir -p logs/ 6 | mkdir -p data/ 7 | chmod a+x box/bin/* 8 | 9 | # config postgres 10 | sed -ri "s!^#?(listen_addresses)\s*=\s*\S+.*!\1 = '*'!" /etc/postgresql/11/main/postgresql.conf 11 | echo 'host all all all md5' >> /etc/postgresql/11/main/pg_hba.conf 12 | mv /var/lib/postgresql/11/main /var/lib/postgresql/11/init 13 | mkdir /var/lib/postgresql/11/main 14 | 15 | # config supervisor 16 | cat box/supervisord.conf > /etc/supervisord.conf 17 | 18 | # config nginx 19 | cat box/nginx/nginx.conf > /etc/nginx/nginx.conf 20 | -------------------------------------------------------------------------------- /rssant_api/migrations/0028_auto_20200619_0906.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-06-19 09:06 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('rssant_api', '0027_storyinfo'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='storyinfo', 16 | name='dt_created', 17 | field=models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /rssant_api/views/errors.py: -------------------------------------------------------------------------------- 1 | from rest_framework.exceptions import APIException, ErrorDetail 2 | from rest_framework.status import HTTP_400_BAD_REQUEST 3 | 4 | 5 | class RssantAPIException(APIException): 6 | 7 | status_code = HTTP_400_BAD_REQUEST 8 | default_detail = 'Invalid request' 9 | default_code = 'invalid' 10 | 11 | def __init__(self, detail=None, code=None): 12 | if detail is None: 13 | detail = self.default_detail 14 | if code is None: 15 | code = self.default_code 16 | self.detail = ErrorDetail(detail, code) 17 | -------------------------------------------------------------------------------- /box/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | current_repo=$(git config --get remote.origin.url) 6 | web_repo=$(python -c ' 7 | import sys; 8 | p=sys.argv[1].rsplit("/",1); 9 | w=p[1].replace("rssant","rssant-web"); 10 | print(p[0]+"/"+w) 11 | ' "$current_repo") 12 | echo "rssant-web: $web_repo" 13 | 14 | if [ ! -d "box/web" ]; then 15 | git clone "$web_repo" box/web 16 | fi 17 | pushd box/web || exit 1 18 | git fetch 19 | git checkout master 20 | git pull --ff-only 21 | popd || exit 1 22 | 23 | # shellcheck disable=SC2068 24 | docker build --progress plain -f box/Dockerfile . $@ 25 | -------------------------------------------------------------------------------- /rssant_api/migrations/0018_feed_freeze_level.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.10 on 2020-03-26 16:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0017_auto_20191217_1253'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feed', 15 | name='freeze_level', 16 | field=models.IntegerField(blank=True, default=1, help_text='freeze level, 1: normal, N: slow down N times', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/http-porn191-com-index-php-feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Contact Support 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/models/test_story_key.py: -------------------------------------------------------------------------------- 1 | from rssant_api.models.story_storage.common.story_key import StoryId, hash_feed_id 2 | 3 | 4 | def test_hash_feed_id(): 5 | for i in [0, 1, 2, 7, 1024, 2**31, 2**32 - 1]: 6 | val = hash_feed_id(i) 7 | assert val >= 0 and val < 2**32 8 | 9 | 10 | def test_story_id(): 11 | cases = [ 12 | (123, 10, 0x7b000000a0), 13 | (123, 1023, 0x7b00003ff0), 14 | ] 15 | for feed_id, offset, story_id in cases: 16 | assert StoryId.encode(feed_id, offset) == story_id 17 | assert StoryId.decode(story_id) == (feed_id, offset) 18 | -------------------------------------------------------------------------------- /rssant/helper/content_hash.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | 4 | from rssant.settings import RSSANT_CONTENT_HASH_METHOD 5 | 6 | 7 | def compute_hash(*fields): 8 | """bytes -> bytes""" 9 | h = hashlib.new(RSSANT_CONTENT_HASH_METHOD) 10 | for content in fields: 11 | if isinstance(content, str): 12 | content = content.encode('utf-8') 13 | h.update(content or b'') 14 | return h.digest() 15 | 16 | 17 | def compute_hash_base64(*fields): 18 | """bytes -> base64 string""" 19 | value = compute_hash(*fields) 20 | return base64.b64encode(value).decode() 21 | -------------------------------------------------------------------------------- /scripts/postgres_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker network create rssant || true 4 | docker volume create rssant_postgres 5 | docker rm -f rssant-postgres 6 | docker run -d \ 7 | --name rssant-postgres \ 8 | --log-driver json-file --log-opt max-size=50m --log-opt max-file=10 \ 9 | --restart unless-stopped \ 10 | --memory=500M \ 11 | --cpus=0.5 \ 12 | --network rssant \ 13 | -p 127.0.0.1:5432:5432 \ 14 | -e "POSTGRES_USER=rssant" \ 15 | -e "POSTGRES_PASSWORD=rssant" \ 16 | -e "POSTGRES_DB=rssant" \ 17 | -v rssant_postgres:/var/lib/postgresql/data \ 18 | postgres:11.7 19 | -------------------------------------------------------------------------------- /rssant_api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from . import models 4 | 5 | 6 | def _register(model): 7 | if not hasattr(model, 'Admin'): 8 | return admin.site.register(m) 9 | 10 | class RssantModelAdmin(admin.ModelAdmin): 11 | if hasattr(model.Admin, 'display_fields'): 12 | list_display = tuple(['id'] + model.Admin.display_fields) 13 | if hasattr(model.Admin, 'search_fields'): 14 | search_fields = model.Admin.search_fields 15 | 16 | return admin.site.register(m, RssantModelAdmin) 17 | 18 | 19 | for m in models.__models__: 20 | _register(m) 21 | -------------------------------------------------------------------------------- /rssant_scheduler/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from aiohttp import web 4 | 5 | from rssant_common.health import health_info 6 | 7 | routes = web.RouteTableDef() 8 | 9 | 10 | def JsonResponse(data: dict): 11 | text = json.dumps(data, ensure_ascii=False, indent=4) 12 | return web.json_response(text=text) 13 | 14 | 15 | @routes.get('/') 16 | async def index(request): 17 | return JsonResponse({'message': '你好,RSS-AIGC Async API!'}) 18 | 19 | 20 | @routes.get('/health') 21 | async def health(request): 22 | result = health_info() 23 | result.update(role='scheduler') 24 | return JsonResponse(result) 25 | -------------------------------------------------------------------------------- /rssant_api/migrations/0056_add_arxiv_report_json_data.py: -------------------------------------------------------------------------------- 1 | # Generated manually 2 | 3 | from django.db import migrations 4 | import django.contrib.postgres.fields.jsonb 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('rssant_api', '0055_create_feishu_bot_send_history'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='arxivreport', 16 | name='report_json_data', 17 | field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='报告JSON数据(论文列表及AI分析)', null=True), 18 | ), 19 | ] 20 | 21 | -------------------------------------------------------------------------------- /rssant/middleware/timer.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from django.http import HttpResponse, HttpRequest 4 | 5 | 6 | LOG = logging.getLogger(__name__) 7 | 8 | 9 | class RssantTimerMiddleware: 10 | def __init__(self, get_response): 11 | self.get_response = get_response 12 | 13 | def __call__(self, request: HttpRequest) -> HttpResponse: 14 | t_begin = time.monotonic() 15 | response = self.get_response(request) 16 | cost_ms = round((time.monotonic() - t_begin) * 1000) 17 | LOG.info(f'X-Time: {cost_ms}ms') 18 | response['X-Time'] = f'{cost_ms}ms' 19 | return response 20 | -------------------------------------------------------------------------------- /tests/test_changelog.py: -------------------------------------------------------------------------------- 1 | from rssant_common.changelog import ChangeLog, ChangeLogList 2 | 3 | 4 | def test_changelog(): 5 | changelog = ChangeLog.from_path('docs/changelog/1.0.0.md') 6 | assert changelog.version == '1.0.0' 7 | assert changelog.date 8 | assert changelog.title 9 | assert changelog.html 10 | 11 | 12 | def test_changelog_list(): 13 | changelog_list = ChangeLogList( 14 | title='RSS-AIGC 更新日志', 15 | link='https://rss.anyant.com/changelog', 16 | directory='docs/changelog', 17 | ) 18 | assert changelog_list.items 19 | assert changelog_list.to_atom() 20 | assert changelog_list.to_html() 21 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/jsonfeed-failed-syntax.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "https://jsonfeed.org/version/1", 3 | "title": "JSON Feed", 4 | "home_page_url": "https://www.v2ex.com/go/jsonfeed", 5 | "feed_url": "https://www.v2ex.com/feed/jsonfeed.json", 6 | "icon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_large.png?m=1567059308", 7 | "favicon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_normal.png?m=1567059308", 8 | "items": [ 9 | { 10 | "author": { 11 | "url": "https://www.v2ex.com/member/DEVN", 12 | "name": "DEVN", 13 | "avatar": "https://cdn.v2ex.com/avatar/6fc1/cc76/467193_large.png?m=1583239760" 14 | }, 15 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # RSSant 配置文件 2 | # 复制此文件为 .env 并填入实际的配置值 3 | 4 | # ============================================ 5 | # AI API 配置 6 | # ============================================ 7 | # AI API 密钥(智谱AI或OpenRouter API Key) 8 | RSSANT_AI_API_KEY=YOUR_AI_API_KEY 9 | 10 | # AI 模型配置(格式:model_id,model_name) 11 | RSSANT_AI_MODEL_CONFIG=glm-z1-flash,glm-z1-flash 12 | 13 | # ============================================ 14 | # AI 搜索和娱乐信息 API 配置 15 | # ============================================ 16 | # Tavily API Key(用于AI影视和AIGC信息搜索) 17 | RSSANT_TAVILY_API_KEY=YOUR_TAVILY_API_KEY 18 | 19 | # 智谱AI API Key(如果与ai_api_key不同可单独配置) 20 | # 如果不配置,将使用 RSSANT_AI_API_KEY 21 | RSSANT_ZHIPU_API_KEY=YOUR_ZHIPU_API_KEY 22 | -------------------------------------------------------------------------------- /tests/sample/inoreader.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Subscriptions from Inoreader [https://www.inoreader.com] 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /rssant_common/blacklist.py: -------------------------------------------------------------------------------- 1 | import re 2 | from urllib.parse import urlparse 3 | 4 | 5 | def _parse_blacklist(text): 6 | lines = set() 7 | for line in text.strip().splitlines(): 8 | if line.strip(): 9 | lines.add(line.strip()) 10 | items = [] 11 | for line in list(sorted(lines)): 12 | items.append(r'((.*\.)?{})'.format(line)) 13 | pattern = re.compile('|'.join(items), re.I) 14 | return pattern 15 | 16 | 17 | def compile_url_blacklist(text): 18 | black_re = _parse_blacklist(text) 19 | 20 | def is_in_blacklist(url): 21 | url = urlparse(url) 22 | return black_re.fullmatch(url.netloc) is not None 23 | 24 | return is_in_blacklist 25 | -------------------------------------------------------------------------------- /etc/apt-sources.list: -------------------------------------------------------------------------------- 1 | deb https://mirrors.aliyun.com/debian/ bullseye main non-free contrib 2 | deb-src https://mirrors.aliyun.com/debian/ bullseye main non-free contrib 3 | deb https://mirrors.aliyun.com/debian-security/ bullseye-security main 4 | deb-src https://mirrors.aliyun.com/debian-security/ bullseye-security main 5 | deb https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib 6 | deb-src https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib 7 | # 阿里云镜像暂未提供 bullseye-backports Release,禁用避免 apt 失败 8 | # deb https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib 9 | # deb-src https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib 10 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | import rssant_common.django_setup # noqa:F401 5 | from rssant_config import CONFIG 6 | from rssant_common.logger import configure_logging 7 | 8 | 9 | if __name__ == '__main__': 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | configure_logging(level=CONFIG.log_level) 19 | execute_from_command_line(sys.argv) 20 | -------------------------------------------------------------------------------- /rssant/allauth_providers/helper.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from rssant_common import _proxy_helper 4 | from rssant_common.rss_proxy import RSSProxyClient, ProxyStrategy 5 | 6 | 7 | LOG = logging.getLogger(__name__) 8 | 9 | 10 | def _proxy_strategy(url): 11 | if 'github.com' in url: 12 | return ProxyStrategy.DIRECT_FIRST 13 | else: 14 | return ProxyStrategy.DIRECT 15 | 16 | 17 | def oauth_api_request(method, url, **kwargs): 18 | """ 19 | when network error, fallback to use rss proxy 20 | """ 21 | options = _proxy_helper.get_proxy_options(url=url) 22 | client = RSSProxyClient(**options, proxy_strategy=_proxy_strategy) 23 | return client.request(method, url, **kwargs) 24 | -------------------------------------------------------------------------------- /rssant_api/migrations/0024_auto_20200510_1008.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-05-10 10:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0023_feed_response_status'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feed', 15 | name='reverse_url', 16 | field=models.TextField(blank=True, help_text='倒转URL', null=True), 17 | ), 18 | migrations.AddIndex( 19 | model_name='feed', 20 | index=models.Index(fields=['reverse_url'], name='rssant_api__reverse_ddd20d_idx'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /rssant_common/standby_domain.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpRequest 2 | from rssant_config import CONFIG 3 | 4 | 5 | def get_request_domain(request: HttpRequest) -> str: 6 | request_host = request and request.headers.get('x-rssant-host') 7 | if not request_host: 8 | request_host = request and request.get_host() 9 | if request_host: 10 | request_domain = request_host.split(':')[0] 11 | if request_domain in CONFIG.standby_domain_set: 12 | return request_domain 13 | return CONFIG.root_domain 14 | 15 | 16 | def get_request_root_url(request: HttpRequest) -> str: 17 | domain = get_request_domain(request) 18 | return CONFIG.root_url.replace(CONFIG.root_domain, domain) 19 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/processor/test_iframe_link.html: -------------------------------------------------------------------------------- 1 |

邱瑞讲租房经历这段太有意思了,头一次感觉数学知识能这么好笑

(视频)



喷嚏网官方App :【安卓】在 豌豆荚 、360手机助手、小米应用商店,搜索:喷嚏阅读;【ios】App store里搜索:喷嚏网官方阅读;

喷嚏网官方网站:http://dapenti.com (海外访问:https://dapenti.com)

每天网络精华尽在【喷嚏图卦】       喷嚏网官方新浪围脖 -------------------------------------------------------------------------------- /rssant_api/models/errors.py: -------------------------------------------------------------------------------- 1 | from .helper import ConcurrentUpdateError 2 | 3 | 4 | class RssantModelError(Exception): 5 | """Base exception for rssant API models""" 6 | 7 | 8 | class FeedExistError(RssantModelError): 9 | """Feed already exists""" 10 | 11 | 12 | class FeedNotFoundError(RssantModelError): 13 | """Feed not found""" 14 | 15 | 16 | class StoryNotFoundError(RssantModelError): 17 | """Story not found""" 18 | 19 | 20 | class FeedStoryOffsetError(RssantModelError): 21 | """Feed story_offset error""" 22 | 23 | 24 | __all__ = ( 25 | 'ConcurrentUpdateError', 26 | 'RssantModelError', 27 | 'FeedExistError', 28 | 'FeedNotFoundError', 29 | 'StoryNotFoundError', 30 | 'FeedStoryOffsetError', 31 | ) 32 | -------------------------------------------------------------------------------- /rssant/middleware/message_storage.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.messages.storage.base import BaseStorage 3 | from django.contrib.messages.storage.cookie import CookieStorage 4 | 5 | 6 | class FakeMessageStorage(BaseStorage): 7 | """A fake messge storage to disable messages middleware""" 8 | 9 | def _get(self, *args, **kwargs): 10 | return [], True 11 | 12 | def _store(self, messages, response, *args, **kwargs): 13 | # see also: CookieStorage._store 14 | cookie_name = CookieStorage.cookie_name 15 | data = self.request.COOKIES.get(cookie_name) 16 | if data: 17 | response.delete_cookie( 18 | cookie_name, domain=settings.SESSION_COOKIE_DOMAIN) 19 | return [] 20 | -------------------------------------------------------------------------------- /rssant_feedlib/dotwhat_data/font.txt: -------------------------------------------------------------------------------- 1 | .FNT - Font FileVery Common 2 | .FON - Font FileVery Common 3 | .OTF - OpenType Font FormatVery Common 4 | .TTF - TrueType FontVery Common 5 | .WOFF - Web Open Font Format FileVery Common 6 | .TTC - TrueType Font Collection FileCommon 7 | .ACFM - Adobe Font Metrics FileAverage 8 | .GDF - PHP GD Library Font FileAverage 9 | .GTF - German Type Foundry FontAverage 10 | .MMM - Adobe Type Manager Multiple-Master Metrics FontAverage 11 | .PFA - PostScript Printer Font ASCII FileAverage 12 | .AMFM - Adobe Multiple Font Metrics Filenot specified 13 | .DFONT - Mac OS X Data Fork Fontnot specified 14 | .EOT - Embedded OpenType Fontnot specified 15 | .GDR - Symbian OS Font Filenot specified 16 | .TPF - Downloadable PCL Soft font filenot specified 17 | -------------------------------------------------------------------------------- /rssant_api/migrations/0032_auto_20201227_0636.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-12-27 06:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0031_story_sentence_count'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feedcreation', 15 | name='group', 16 | field=models.CharField(blank=True, help_text='用户设置的分组', max_length=200, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='feedcreation', 20 | name='title', 21 | field=models.CharField(blank=True, help_text='用户设置的标题', max_length=200, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /rssant_api/migrations/0016_auto_20191105_1247.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.6 on 2019-11-05 12:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0015_story_has_mathjax'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='feed', 15 | name='dryness', 16 | field=models.IntegerField(blank=True, default=0, help_text='Dryness of the feed', null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='feed', 20 | name='dt_first_story_published', 21 | field=models.DateTimeField(blank=True, help_text='最老的story发布时间', null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /tests/feedlib/testdata/parser/failed/v2ex-jsonfeed-no-storys.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "https://jsonfeed.org/version/1", 3 | "title": "JSON Feed", 4 | "description": "\u7c7b\u4f3c RSS \u7684\u7ad9\u70b9\u5185\u5bb9\u4fe1\u606f\u6d41 JSON \u683c\u5f0f\u3002\u8fd9\u91cc\u8ba8\u8bba JSON Feed \u7684\u5b9e\u73b0\u53ca\u9605\u8bfb\u5668\u652f\u6301\u3002", 5 | "home_page_url": "https://www.v2ex.com/go/jsonfeed", 6 | "feed_url": "https://www.v2ex.com/feed/jsonfeed.json", 7 | "icon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_large.png?m=1567059308", 8 | "favicon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_normal.png?m=1567059308", 9 | "items": [ 10 | { 11 | "date_published": "2020-01-31T15:05:16+00:00", 12 | "content_html": "2020-01-31" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /tests/models/test_story_unique_ids.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from rssant_api.models.story_unique_ids import StoryUniqueIdsData 3 | 4 | 5 | CASES = { 6 | 'empty': (0, []), 7 | 'one': (1, [ 8 | '93C07B6C-D848-4405-A349-07A3775FA0A9', 9 | ]), 10 | 'two': (3, [ 11 | 'https://www.example.com/2.html', 12 | 'https://www.example.com/3.html', 13 | ]) 14 | } 15 | 16 | 17 | @pytest.mark.parametrize('case_name', list(CASES)) 18 | def test_encode_decode(case_name): 19 | begin_offset, unique_ids = CASES[case_name] 20 | data = StoryUniqueIdsData(begin_offset, unique_ids=unique_ids) 21 | data_bytes = data.encode() 22 | got = StoryUniqueIdsData.decode(data_bytes) 23 | assert got.begin_offset == begin_offset 24 | assert got.unique_ids == unique_ids 25 | -------------------------------------------------------------------------------- /cursor/docs/项目概述.md: -------------------------------------------------------------------------------- 1 | # 项目概述 2 | 3 | ## 项目背景 4 | 5 | - RSS-AIGC 系统,结合 AI 报告、飞书机器人等业务能力,支持本地与容器化部署。 6 | 7 | ## 项目目标 8 | 9 | - 提供稳定的 RSS 订阅与全文阅读能力,支持跨平台阅读、收藏与已读管理。 10 | - 集成 AI 行业搜索与报告生成、飞书机器人定时推送等增值功能。 11 | 12 | ## 功能概述 13 | 14 | - 订阅源管理、文章读取与状态管理(收藏、已读)。 15 | - 行业资讯聚合(Hacker News/GitHub/ArXiv)与 AI 内容加工。 16 | - AI 娱乐与 AIGC 报告生成与导出(例如导出到飞书文档)。 17 | - 飞书机器人配置管理与定时推送。 18 | - 前后端分离,后端提供 REST API 与 Swagger 文档。 19 | 20 | ## 技术栈 21 | 22 | - 后端:Django、DRF、AllAuth、Whitenoise、PostgreSQL、supervisor。 23 | - 前端:React 18、TypeScript、Vite、Tailwind、Radix UI/shadcn、Zustand。 24 | - 调度与多角色:`api/worker/scheduler` 多角色运行,`aiohttp` 服务(rssant_scheduler)。 25 | - 容器与部署:Docker 多阶段构建、supervisord 管理进程、脚本化部署。 26 | 27 | ## 架构类型 28 | 29 | - 未使用 GBF 框架;后端采用 Django + DRF 的分层组织,领域模型以 Django ORM 模型为核心,配合 `UnionFeed/UnionStory` 聚合访问层与服务层。 -------------------------------------------------------------------------------- /rssant_api/migrations/0044_add_citation_fields.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2025-11-17 02:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rssant_api', '0043_add_arxiv_multidimensional_scores'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='arxivpaper', 15 | name='citation_count', 16 | field=models.IntegerField(blank=True, help_text='引用数(来自 Semantic Scholar)', null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='arxivpaper', 20 | name='dt_citation_updated', 21 | field=models.DateTimeField(blank=True, help_text='引用数更新时间', null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /rssant_api/migrations/0047_add_ai_entertainment_refs.py: -------------------------------------------------------------------------------- 1 | # Generated manually 2 | 3 | from django.db import migrations 4 | import jsonfield.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('rssant_api', '0046_add_ai_entertainment_report'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='aientertainmentreport', 16 | name='ai_entertainment_refs', 17 | field=jsonfield.fields.JSONField(default=list, help_text='AI影视参考链接列表'), 18 | ), 19 | migrations.AddField( 20 | model_name='aientertainmentreport', 21 | name='aigc_refs', 22 | field=jsonfield.fields.JSONField(default=list, help_text='AIGC参考链接列表'), 23 | ), 24 | ] 25 | 26 | -------------------------------------------------------------------------------- /frontend/src/store/useServiceClientStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | import { persist, createJSONStorage } from 'zustand/middleware' 3 | 4 | interface ServiceClientState { 5 | serviceSecret: string 6 | setServiceSecret: (secret: string) => void 7 | clearServiceSecret: () => void 8 | } 9 | 10 | export const useServiceClientStore = create()( 11 | persist( 12 | (set) => ({ 13 | serviceSecret: '', 14 | setServiceSecret: (secret: string) => { 15 | set({ serviceSecret: (secret || '').trim() }) 16 | }, 17 | clearServiceSecret: () => { 18 | set({ serviceSecret: '' }) 19 | }, 20 | }), 21 | { 22 | name: 'service-client-store', 23 | storage: createJSONStorage(() => localStorage), 24 | }, 25 | ), 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /tests/sample/stringer.opml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Feeds from Stringer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as LabelPrimitive from '@radix-ui/react-label' 3 | import { cva, type VariantProps } from 'class-variance-authority' 4 | import { cn } from '@/lib/utils' 5 | 6 | const labelVariants = cva( 7 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' 8 | ) 9 | 10 | const Label = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef & 13 | VariantProps 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | Label.displayName = LabelPrimitive.Root.displayName 22 | 23 | export { Label } 24 | 25 | -------------------------------------------------------------------------------- /rssant/templates/email/recall.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Hi {{ username }} 好久不见!
4 |

5 |
是否还记得一年以前,系统刚刚上线时的模样?
6 |

7 |
在这一年多时间里,我一直在不断完善系统的功能和体验,如今系统已焕然一新!
8 |

9 |
10 | 系统依旧是无广告,无推荐,专注阅读的 RSS 阅读器。 11 | 全新界面设计,现已适配桌面端,也支持添加到手机主屏。 12 | 支持全文阅读,图片代理,播客等等,全球 RSS 均可订阅。 13 |
14 |

15 |
如果你需要一款好用的 RSS 阅读器,轻松订阅博客和资讯,欢迎回来看看!
16 |
开箱即用地址:https://rss.anyant.com/
17 |

18 |
使用中如有任何问题,欢迎随时联系我!
19 |

20 |
guyskk
21 |
系统开发者
22 |
-------------------------------------------------------------------------------- /rssant_common/django_setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | https://stackoverflow.com/questions/39704298/how-to-call-django-setup-in-console-script 3 | """ 4 | import os 5 | from pathlib import Path 6 | import django 7 | from django.apps import apps 8 | from django.conf import settings 9 | from django.utils.autoreload import autoreload_started 10 | 11 | 12 | _root_dir = Path(__file__).parent.parent 13 | 14 | 15 | def _watch_changelog(sender, **kwargs): 16 | sender.watch_dir(_root_dir / 'docs/changelog', '*') 17 | sender.watch_dir(_root_dir / 'rssant_common/resources', '*') 18 | 19 | 20 | def _django_setup(): 21 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rssant.settings') 22 | if not apps.ready and not settings.configured: 23 | django.setup() 24 | autoreload_started.connect(_watch_changelog) 25 | 26 | 27 | _django_setup() 28 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | /* Path aliases */ 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": ["./src/*"] 27 | } 28 | }, 29 | "include": ["src"], 30 | "references": [{ "path": "./tsconfig.node.json" }] 31 | } 32 | 33 | -------------------------------------------------------------------------------- /tests/feedlib/test_response.py: -------------------------------------------------------------------------------- 1 | from rssant_feedlib.response import FeedResponseStatus, FeedResponse, FeedContentType 2 | 3 | 4 | def test_response_status(): 5 | status = FeedResponseStatus(-200) 6 | assert status == -200 7 | assert status in (-200, -300) 8 | assert FeedResponseStatus.name_of(200) == 'OK' 9 | assert FeedResponseStatus.name_of(600) == 'HTTP_600' 10 | assert FeedResponseStatus.name_of(-200) == 'FEED_CONNECTION_ERROR' 11 | assert FeedResponseStatus.is_need_proxy(-200) 12 | 13 | 14 | def test_response_repr(): 15 | response = FeedResponse( 16 | content=b'123456', 17 | url='https://example.com/feed.xml', 18 | feed_type=FeedContentType.XML, 19 | ) 20 | assert repr(response) 21 | response = FeedResponse( 22 | url='https://example.com/feed.xml', 23 | ) 24 | assert repr(response) 25 | -------------------------------------------------------------------------------- /rssant_common/health.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from rssant_common import timezone 4 | from rssant_common.network_helper import LOCAL_IP_LIST 5 | 6 | UPTIME_BEGIN = timezone.now() 7 | 8 | 9 | def _get_uptime(now: timezone.datetime): 10 | uptime_seconds = round((now - UPTIME_BEGIN).total_seconds()) 11 | uptime = str(timezone.timedelta(seconds=uptime_seconds)) 12 | return uptime 13 | 14 | 15 | def health_info(): 16 | build_id = os.getenv("EZFAAS_BUILD_ID") 17 | commit_id = os.getenv("EZFAAS_COMMIT_ID") 18 | now = timezone.now() 19 | uptime = _get_uptime(now) 20 | ip_list = [ip for _, ip in LOCAL_IP_LIST] 21 | result = dict( 22 | build_id=build_id, 23 | commit_id=commit_id, 24 | now=now.isoformat(), 25 | uptime=uptime, 26 | pid=os.getpid(), 27 | ip_list=ip_list, 28 | ) 29 | return result 30 | -------------------------------------------------------------------------------- /rssant_common/signature.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from validr import T 3 | 4 | 5 | def get_params(f): 6 | sig = inspect.signature(f) 7 | params_schema = {} 8 | for name, p in list(sig.parameters.items())[1:]: 9 | if p.default is not inspect.Parameter.empty: 10 | raise ValueError('You should not set default in schema annotation!') 11 | if p.annotation is inspect.Parameter.empty: 12 | raise ValueError(f'Missing annotation in parameter {name}!') 13 | params_schema[name] = p.annotation 14 | if params_schema: 15 | return T.dict(params_schema).__schema__ 16 | return None 17 | 18 | 19 | def get_returns(f): 20 | sig = inspect.signature(f) 21 | if sig.return_annotation is not inspect.Signature.empty: 22 | schema = sig.return_annotation 23 | return T(schema).__schema__ 24 | return None 25 | -------------------------------------------------------------------------------- /box/bin/start-postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | /usr/lib/postgresql/11/bin/postgres --version 6 | 7 | if [ ! "$(ls -A /var/lib/postgresql/11/main)" ]; then 8 | echo 'copy postgresql init data to mounted volume' 9 | cp -a /var/lib/postgresql/11/init/. /var/lib/postgresql/11/main 10 | fi 11 | 12 | # the uid:gid will change after upgrade docker image 13 | # volume permission may mismatch and need fix 14 | # eg: when upgrade docker image from debian-9 to debian-10 15 | chown -R postgres:postgres /var/lib/postgresql/11/main 16 | chmod 700 /var/lib/postgresql/11/main 17 | chown -R postgres:postgres /var/log/postgresql 18 | chmod 700 /var/log/postgresql 19 | 20 | rm -f /var/lib/postgresql/11/main/postmaster.pid 21 | su -c "/usr/lib/postgresql/11/bin/postgres \ 22 | -D /var/lib/postgresql/11/main \ 23 | -c config_file=/etc/postgresql/11/main/postgresql.conf" \ 24 | postgres 25 | -------------------------------------------------------------------------------- /frontend/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { cn } from '@/lib/utils' 3 | 4 | export interface TextareaProps 5 | extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |