diff --git a/docs/picture.md b/docs/picture.md
index 49b289ed5..a5879aa72 100644
--- a/docs/picture.md
+++ b/docs/picture.md
@@ -64,6 +64,16 @@ pageClass: routes
+## 500px 摄影社区
+
+### 部落影集
+
+
+
+### 摄影师作品
+
+
+
## 8KCosplay
### 最新
diff --git a/lib/v2/500px/maintainer.js b/lib/v2/500px/maintainer.js
new file mode 100644
index 000000000..e2aa1857b
--- /dev/null
+++ b/lib/v2/500px/maintainer.js
@@ -0,0 +1,4 @@
+module.exports = {
+ '/tribe/set/:id': ['TonyRL'],
+ '/user/works/:id': ['TonyRL'],
+};
diff --git a/lib/v2/500px/radar.js b/lib/v2/500px/radar.js
new file mode 100644
index 000000000..1f2202b76
--- /dev/null
+++ b/lib/v2/500px/radar.js
@@ -0,0 +1,19 @@
+module.exports = {
+ '500px.com.cn': {
+ _name: '500px 摄影社区',
+ '.': [
+ {
+ title: '部落影集',
+ docs: 'https://docs.rsshub.app/picture.html#_500px-she-ying-she-qu',
+ source: ['/page/tribe/detail'],
+ target: (_, url) => `/500px/tribe/set/${url.searchParams.get('tribeId')}`,
+ },
+ {
+ title: '摄影师作品',
+ docs: 'https://docs.rsshub.app/picture.html#_500px-she-ying-she-qu',
+ source: ['/:id', '/community/user-details/:id', '/community/user-details/:id/*'],
+ target: '/500px/user/works/:id',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/500px/router.js b/lib/v2/500px/router.js
new file mode 100644
index 000000000..bb34cfef1
--- /dev/null
+++ b/lib/v2/500px/router.js
@@ -0,0 +1,4 @@
+module.exports = (router) => {
+ router.get('/tribe/set/:id', require('./tribeSet'));
+ router.get('/user/works/:id', require('./user'));
+};
diff --git a/lib/v2/500px/templates/tribeSet.art b/lib/v2/500px/templates/tribeSet.art
new file mode 100644
index 000000000..c9d7688fb
--- /dev/null
+++ b/lib/v2/500px/templates/tribeSet.art
@@ -0,0 +1,6 @@
+{{ if item.description }}
{{ item.description }}
{{ /if }}
+{{ if item.photos }}
+ {{ each item.photos p }}
+
+ {{ /each }}
+{{ /if }}
diff --git a/lib/v2/500px/templates/user.art b/lib/v2/500px/templates/user.art
new file mode 100644
index 000000000..0499ef506
--- /dev/null
+++ b/lib/v2/500px/templates/user.art
@@ -0,0 +1,3 @@
+{{ if item.url }}
+
+{{ /if }}
diff --git a/lib/v2/500px/tribeSet.js b/lib/v2/500px/tribeSet.js
new file mode 100644
index 000000000..aba1ace53
--- /dev/null
+++ b/lib/v2/500px/tribeSet.js
@@ -0,0 +1,29 @@
+const { parseDate } = require('@/utils/parse-date');
+const { art } = require('@/utils/render');
+const path = require('path');
+
+const { baseUrl, getTribeDetail, getTribeSets } = require('./utils');
+
+module.exports = async (ctx) => {
+ const { id } = ctx.params;
+ const limit = parseInt(ctx.query.limit) || 100;
+
+ const { tribe } = await getTribeDetail(id, ctx.cache.tryGet);
+ const tribeSets = await getTribeSets(id, limit, ctx.cache.tryGet);
+
+ const items = tribeSets.map((item) => ({
+ title: item.title,
+ description: art(path.join(__dirname, 'templates/tribeSet.art'), { item }),
+ author: item.uploaderInfo.nickName,
+ pubDate: parseDate(item.createdTime, 'x'),
+ link: `${baseUrl}/community/set/${item.id}/details`,
+ }));
+
+ ctx.state.data = {
+ title: tribe.name,
+ description: `${tribe.watchword} - ${tribe.introduce}`,
+ link: `${baseUrl}/page/tribe/detail?tribeId=${id}&pagev=set`,
+ image: tribe.avatar.a1,
+ item: items,
+ };
+};
diff --git a/lib/v2/500px/user.js b/lib/v2/500px/user.js
new file mode 100644
index 000000000..c20c0f705
--- /dev/null
+++ b/lib/v2/500px/user.js
@@ -0,0 +1,32 @@
+const { art } = require('@/utils/render');
+const { parseDate } = require('@/utils/parse-date');
+const path = require('path');
+const { baseUrl, getUserInfoFromUsername, getUserInfoFromId, getUserWorks } = require('./utils');
+
+module.exports = async (ctx) => {
+ let { id } = ctx.params;
+ const limit = parseInt(ctx.query.limit) || 100;
+
+ if (id.length !== 33) {
+ id = (await getUserInfoFromUsername(id, ctx.cache.tryGet)).id;
+ }
+
+ const userInfo = await getUserInfoFromId(id, ctx.cache.tryGet);
+ const userWorks = await getUserWorks(id, limit, ctx.cache.tryGet);
+
+ const items = userWorks.map((item) => ({
+ title: item.title || '无题',
+ description: art(path.join(__dirname, 'templates/user.art'), { item }),
+ author: item.uploaderInfo.nickName,
+ pubDate: parseDate(item.createdTime, 'x'),
+ link: `${baseUrl}/community/photo-details/${item.id}`,
+ }));
+
+ ctx.state.data = {
+ title: userInfo.nickName,
+ description: userInfo.about,
+ image: userInfo.avatar.a1,
+ link: `${baseUrl}/${id}`,
+ item: items,
+ };
+};
diff --git a/lib/v2/500px/utils.js b/lib/v2/500px/utils.js
new file mode 100644
index 000000000..91bb641f6
--- /dev/null
+++ b/lib/v2/500px/utils.js
@@ -0,0 +1,122 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const config = require('@/config').value;
+
+const baseUrl = 'https://500px.com.cn';
+
+/*
+ty_rum.server = {
+ id: 'Fm3hXcTiLT8',
+ ignore_err: true,
+ beacon: 'beacon.tingyun.com',
+ beacon_err: 'beacon-err.tingyun.com',
+ key: 'M1laN7-zRM0',
+ trace_threshold: 7000,
+ custom_urls: [],
+ sr: 1.0,
+};
+v && v.id && this._ty_rum && y(this._ty_rum.url) && (this._ty_rum.r = (new Date).getTime() % 1e8,
+this.setRequestHeader && this.setRequestHeader("X-Tingyun-Id", v.id + ";r=" + this._ty_rum.r))
+*/
+const headers = {
+ // {"anonymousId":"","latitude":null,"longitude":null,"manufacturer":"","province":"","area":"","city":""}
+ 'x-500px-client-info': 'eyJhbm9ueW1vdXNJZCI6IiIsImxhdGl0dWRlIjpudWxsLCJsb25naXR1ZGUiOm51bGwsIm1hbnVmYWN0dXJlciI6IiIsInByb3ZpbmNlIjoiIiwiYXJlYSI6IiIsImNpdHkiOiIifQ==',
+ 'X-Tingyun-Id': `Fm3hXcTiLT8;r=${new Date().getTime() % 1e8}`,
+};
+
+const getUserInfoFromUsername = (username, tryGet) =>
+ tryGet(`500px:user:${username}`, async () => {
+ const { data } = await got(`${baseUrl}/${username}`);
+ const $ = cheerio.load(data);
+ return JSON.parse(
+ $('script[type="text/javascript"]')
+ .text()
+ .match(/var cur_user = new Object\((.*?)\);/)[1]
+ );
+ });
+
+const getUserInfoFromId = (id, tryGet) =>
+ tryGet(`500px:user:indexInfo:${id}`, async () => {
+ const { data } = await got(`${baseUrl}/community/v2/user/indexInfo`, {
+ headers: {
+ ...headers,
+ },
+ searchParams: {
+ queriedUserId: id,
+ },
+ });
+ return data.data;
+ });
+
+const getUserWorks = (id, limit, tryGet) =>
+ tryGet(
+ `500px:user:profile:${id}`,
+ async () => {
+ const { data } = await got(`${baseUrl}/community/v2/user/profile`, {
+ headers: {
+ ...headers,
+ },
+ searchParams: {
+ resourceType: '0,2,4',
+ imgsize: 'p1,p2,p3,p4',
+ queriedUserId: id,
+ startTime: '',
+ page: 1,
+ size: limit,
+ type: 'json',
+ },
+ });
+ return data.data;
+ },
+ config.cache.routeExpire,
+ false
+ );
+
+const getTribeDetail = (id, tryGet) =>
+ tryGet(
+ `500px:tribeDetail:${id}`,
+ async () => {
+ const { data } = await got(`${baseUrl}/community/tribe/tribeDetail`, {
+ headers: {
+ ...headers,
+ },
+ searchParams: {
+ tribeId: id,
+ },
+ });
+ return data.data;
+ },
+ config.cache.routeExpire,
+ false
+ );
+
+const getTribeSets = (id, limit, tryGet) =>
+ tryGet(
+ `500px:tribeSets:${id}`,
+ async () => {
+ const { data } = await got(`${baseUrl}/community/tribe/getTribeSetsV2`, {
+ headers: {
+ ...headers,
+ },
+ searchParams: {
+ tribeId: id,
+ privacy: 1,
+ page: 1,
+ size: limit,
+ type: 'json',
+ },
+ });
+ return data.data;
+ },
+ config.cache.routeExpire,
+ false
+ );
+
+module.exports = {
+ baseUrl,
+ getUserInfoFromUsername,
+ getUserInfoFromId,
+ getUserWorks,
+ getTribeDetail,
+ getTribeSets,
+};