diff --git a/docs/en/other.md b/docs/en/other.md
index d88422ede..79a9f6239 100644
--- a/docs/en/other.md
+++ b/docs/en/other.md
@@ -214,6 +214,12 @@ For example:
+## oshwhub
+
+### OpenSource Square
+
+
+
## Panda
### Feeds
diff --git a/docs/other.md b/docs/other.md
index c79f045a7..c13c8675e 100644
--- a/docs/other.md
+++ b/docs/other.md
@@ -629,6 +629,12 @@ type 为 all 时,category 参数不支持 cost 和 free
+## 立创开源硬件平台
+
+### 开源广场
+
+
+
## 律师事务所文章
### 君合
diff --git a/lib/v2/oshwhub/explore.js b/lib/v2/oshwhub/explore.js
new file mode 100644
index 000000000..d82ec6b8f
--- /dev/null
+++ b/lib/v2/oshwhub/explore.js
@@ -0,0 +1,116 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const path = require('path');
+const { art } = require('@/utils/render');
+const timezone = require('@/utils/timezone');
+const { parseDate } = require('@/utils/parse-date');
+const md = require('markdown-it')({
+ html: true,
+ linkify: true,
+});
+
+const requestImages = (url, tryGet) =>
+ tryGet(url, async () => {
+ const response = await got({
+ method: 'get',
+ url,
+ });
+
+ const images = {};
+ const boms = [];
+
+ const imgData = response.data;
+ imgData?.result?.boards?.forEach((board) => {
+ const name = board.name;
+ if (images[name] === undefined) {
+ images[name] = [];
+ }
+
+ board.schematic?.documents?.forEach((val) => {
+ if (val.thumb) {
+ images[name].push(val.thumb);
+ }
+ });
+
+ if (board.pcb_info?.thumb) {
+ images[name].push(board.pcb_info.thumb);
+ }
+ if (board.pcb_info?.boms?.boms) {
+ boms.push(...JSON.parse(board.pcb_info.boms.boms));
+ }
+ });
+ return { images, boms };
+ });
+
+module.exports = async (ctx) => {
+ const baseUrl = 'https://oshwhub.com';
+ const sortType = ctx.params.sortType ?? 'updatedTime';
+
+ const url = `${baseUrl}/explore?projectSort=${sortType}`;
+ const response = await got({
+ method: 'get',
+ url,
+ });
+
+ const html = response.data;
+ const $ = cheerio.load(html);
+ const title = $('title').text();
+ const projects = $('a[class="project-link"]');
+
+ const items = await Promise.all(
+ projects.map((id, item) => {
+ const detailUrl = `${baseUrl}${item.attribs.href}`;
+ return ctx.cache.tryGet(detailUrl, async () => {
+ const response = await got({
+ method: 'get',
+ url: detailUrl,
+ });
+
+ const html = response.data;
+ const $$ = cheerio.load(html);
+ const projectDetail = $$('div[class="projects-left-top"]');
+
+ const title = $$(projectDetail).find('span[class="title"]').text();
+ const author = $$(projectDetail).find('div[class="member-items"] > ul > li > a > span').first().text().trim();
+ const publishTime = $$(projectDetail).find('div[class="issue-time"] > span').text();
+ const pubDate = timezone(parseDate(publishTime.trim().split('\n')[1].trim(), 'YYYY/MM/DD HH:mm:ss'), 0);
+
+ const coverImg = $$(projectDetail).find('div[class="img-cover"] > img').attr('src').trim();
+ const introduction = $$(projectDetail).find('p[class="introduction"]').text().trim();
+ const content = $$(projectDetail).find('div[class*="html-content"]').attr('data-markdown');
+
+ // request images
+ const dataUuid = $$('div[class="projects-detail"]').attr()['data-uuid'];
+ const { images, boms } = await requestImages(`${baseUrl}/api/project/${dataUuid}/pro_images`, ctx.cache.tryGet);
+
+ const description = art(path.join(__dirname, 'templates/description.art'), {
+ title,
+ author,
+ publishTime,
+ coverImg,
+ introduction,
+ content: md.render(content) || 'No Content',
+ images,
+ boms,
+ });
+
+ const single = {
+ title,
+ description,
+ pubDate,
+ link: detailUrl,
+ author,
+ };
+
+ return single;
+ });
+ })
+ );
+
+ ctx.state.data = {
+ title,
+ link: url,
+ description: title,
+ item: items,
+ };
+};
diff --git a/lib/v2/oshwhub/maintainer.js b/lib/v2/oshwhub/maintainer.js
new file mode 100644
index 000000000..ff8668b06
--- /dev/null
+++ b/lib/v2/oshwhub/maintainer.js
@@ -0,0 +1,3 @@
+module.exports = {
+ '/:sortType?': ['tylinux'],
+};
diff --git a/lib/v2/oshwhub/radar.js b/lib/v2/oshwhub/radar.js
new file mode 100644
index 000000000..5176c22e2
--- /dev/null
+++ b/lib/v2/oshwhub/radar.js
@@ -0,0 +1,16 @@
+module.exports = {
+ 'oshwhub.com': {
+ _name: '立创开源硬件平台',
+ '.': [
+ {
+ title: '开源广场',
+ docs: 'https://docs.rsshub.app/other.html#li-chuang-kai-yuan-ying-jian-ping-tai',
+ source: ['/explore'],
+ target: (_, url) => {
+ const sortType = new URL(url).searchParams.get('projectSort');
+ return sortType ? `/oshwhub/${sortType}` : '';
+ },
+ },
+ ],
+ },
+};
diff --git a/lib/v2/oshwhub/router.js b/lib/v2/oshwhub/router.js
new file mode 100644
index 000000000..b112bc945
--- /dev/null
+++ b/lib/v2/oshwhub/router.js
@@ -0,0 +1,3 @@
+module.exports = function (router) {
+ router.get('/:sortType?', require('./explore'));
+};
diff --git a/lib/v2/oshwhub/templates/description.art b/lib/v2/oshwhub/templates/description.art
new file mode 100644
index 000000000..cb6027fca
--- /dev/null
+++ b/lib/v2/oshwhub/templates/description.art
@@ -0,0 +1,58 @@
+
+
+

+
{{publishTime}}
+
作者:{{author}}
+
{{introduction}}
+
+
+
+
描述
+ {{@ content}}
+
+
+ {{ if images }}
+
+
PCB && schematic
+ {{each images $imgs $key}}
+ {{each $imgs $img}}
+

+ {{/each}}
+ {{/each}}
+
+ {{/if}}
+
+ {{ if boms }}
+
+
BOM
+
+
+ | No |
+ Quantity |
+ Device |
+ Designator |
+ Footprint |
+ Value |
+ Manufacturer Part |
+ Manufacturer |
+ Supplier Part |
+ Supplier |
+
+ {{each boms}}
+
+ | {{$value.No}} |
+ {{$value.Quantity}} |
+ {{$value.Device}} |
+ {{$value.Designator}} |
+ {{$value.Footprint}} |
+ {{$value.Value}} |
+ {{$value['Manufacturer Part']}} |
+ {{$value.Manufacturer}} |
+ {{$value['Supplier Part']}} |
+ {{$value.Supplier}} |
+
+ {{/each}}
+
+
+ {{/if}}
+
\ No newline at end of file