From 4f6501c6002b5575980a8f6c0130dfffa85847be Mon Sep 17 00:00:00 2001 From: rjnishant530 Date: Sat, 4 Jan 2025 18:19:45 +0530 Subject: [PATCH 1/2] init --- lib/routes/30secondsofcode/category.ts | 65 +++++++++++++++++++ lib/routes/30secondsofcode/namespace.ts | 7 ++ lib/routes/30secondsofcode/new-and-popular.ts | 41 ++++++++++++ lib/routes/30secondsofcode/utils.ts | 54 +++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 lib/routes/30secondsofcode/category.ts create mode 100644 lib/routes/30secondsofcode/namespace.ts create mode 100644 lib/routes/30secondsofcode/new-and-popular.ts create mode 100644 lib/routes/30secondsofcode/utils.ts diff --git a/lib/routes/30secondsofcode/category.ts b/lib/routes/30secondsofcode/category.ts new file mode 100644 index 00000000000000..e202e3972610a1 --- /dev/null +++ b/lib/routes/30secondsofcode/category.ts @@ -0,0 +1,65 @@ +import { Data, Route } from '@/types'; +import { load } from 'cheerio'; +import ofetch from '@/utils/ofetch'; +import { processList } from './utils'; +export const route: Route = { + path: '/:category?/:subCategory?', + categories: ['programming', 'popular'], + example: '/css/interactivity', + parameters: { + category: { + description: 'Main Category. For Complete list visit site "https://www.30secondsofcode.org/collections/p/1/"', + options: [ + { value: 'js', label: 'Javascript' }, + { value: 'css', label: 'CSS' }, + { value: 'algorithm', label: 'JavaScript Algorithms' }, + { value: 'react', label: 'React' }, + ], + }, + subCategory: { + description: 'Filter within Category. Visit Individual Category site for subCategories', + }, + }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['30secondsofcode.org/:category/:subCategory/', '30secondsofcode.org/:category/'], + target: '/:category/:subCategory', + }, + ], + name: 'Category and Subcategory', + maintainers: ['Rjnishant530'], + handler, +}; + +async function handler(ctx) { + const category = ctx.req.param('category') ?? ''; + const subCategory = ctx.req.param('subCategory') ?? ''; + + const rootUrl = 'https://www.30secondsofcode.org'; + const currentUrl = `${rootUrl}${category ? `/${category}` : ''}${subCategory ? `/${subCategory}` : ''}${category || subCategory ? '/p/1/' : ''}`; + + const response = await ofetch(currentUrl); + const $ = load(response); + const heroElement = $('section.hero'); + const heading = heroElement.find('div > h1').text(); + const description = heroElement.find('div > p').text(); + const image = heroElement.find('img').attr('src'); + + const fullList = $('section.preview-list > ul > li').toArray(); + const items = await processList(fullList); + return { + title: heading, + description, + image: `${rootUrl}${image}`, + link: rootUrl, + item: items, + } as Data; +} diff --git a/lib/routes/30secondsofcode/namespace.ts b/lib/routes/30secondsofcode/namespace.ts new file mode 100644 index 00000000000000..40839e5fa75321 --- /dev/null +++ b/lib/routes/30secondsofcode/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '30 Seconds of code', + url: '30secondsofcode.org', + lang: 'en', +}; diff --git a/lib/routes/30secondsofcode/new-and-popular.ts b/lib/routes/30secondsofcode/new-and-popular.ts new file mode 100644 index 00000000000000..985b1c40088f18 --- /dev/null +++ b/lib/routes/30secondsofcode/new-and-popular.ts @@ -0,0 +1,41 @@ +import { Data, Route } from '@/types'; +import { load } from 'cheerio'; +import { processList, rootUrl } from './utils'; +import ofetch from '@/utils/ofetch'; + +export const route: Route = { + path: '/', + categories: ['programming', 'popular'], + example: '/', + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['30secondsofcode.org'], + target: '/', + }, + ], + name: 'New & Popular Snippets', + maintainers: ['Rjnishant530'], + handler, +}; + +async function handler() { + const response = await ofetch(rootUrl); + + const $ = load(response); + const fullList = $('section.preview-list > ul > li').toArray(); + const items = await processList(fullList); + return { + title: 'New & Popular Snippets', + description: 'Discover short code snippets for all your development needs.', + link: rootUrl, + item: items, + } as Data; +} diff --git a/lib/routes/30secondsofcode/utils.ts b/lib/routes/30secondsofcode/utils.ts new file mode 100644 index 00000000000000..acb3484ad92ca6 --- /dev/null +++ b/lib/routes/30secondsofcode/utils.ts @@ -0,0 +1,54 @@ +import { DataItem } from '@/types'; +import { load } from 'cheerio'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import cache from '@/utils/cache'; + +export const rootUrl = 'https://www.30secondsofcode.org'; + +export async function processList(listElements) { + const items = await Promise.allSettled( + listElements.map((item) => { + const $ = load(item); + const link = $(' article > h3 > a').attr('href'); + const date = $(' article > small > time').attr('datetime'); + return processItem({ link, date }); + }) + ); + return items.map((item) => (item.status === 'fulfilled' ? item.value : ({ title: 'Error Reading Item' } as DataItem))); +} + +async function processItem({ link: articleLink, date }) { + return await cache.tryGet(`30secondsofcode:${articleLink}`, async () => { + const finalLink = `${rootUrl}${articleLink}`; + const response = await ofetch(finalLink); + const $ = load(response); + const tags = $.root() + .find('body > main > nav > ol > li:not(:first-child):not(:last-child)') + .toArray() + .map((tag) => $(tag).find('a').text()); + const article = $('main > article'); + const title = article.find('h1').text(); + article.find('img').each((_, element) => { + const img = $(element); + const src = img.attr('src'); + if (src?.startsWith('/')) { + img.attr('src', `${rootUrl}${src}`); + } + }); + const image = article.find('img').attr('src'); + const description = article.clone().find('script').remove().end().html(); + + return { + title, + link: finalLink, + pubDate: parseDate(date), + description, + author: '30 Seconds of Code', + category: tags, + image: `${rootUrl}${image}`, + banner: `${rootUrl}${image}`, + language: 'en-us', + } as DataItem; + }); +} From b2f4a088c74a18af7f5f947cc1c71070aff6cdce Mon Sep 17 00:00:00 2001 From: rjnishant530 Date: Sun, 5 Jan 2025 21:58:14 +0530 Subject: [PATCH 2/2] comment fixes --- lib/routes/30secondsofcode/category.ts | 8 ++++---- lib/routes/30secondsofcode/namespace.ts | 2 +- lib/routes/30secondsofcode/new-and-popular.ts | 8 ++++---- lib/routes/30secondsofcode/utils.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/routes/30secondsofcode/category.ts b/lib/routes/30secondsofcode/category.ts index e202e3972610a1..430c480f706078 100644 --- a/lib/routes/30secondsofcode/category.ts +++ b/lib/routes/30secondsofcode/category.ts @@ -3,9 +3,9 @@ import { load } from 'cheerio'; import ofetch from '@/utils/ofetch'; import { processList } from './utils'; export const route: Route = { - path: '/:category?/:subCategory?', - categories: ['programming', 'popular'], - example: '/css/interactivity', + path: '/category/:category?/:subCategory?', + categories: ['programming'], + example: '/category/css/interactivity', parameters: { category: { description: 'Main Category. For Complete list visit site "https://www.30secondsofcode.org/collections/p/1/"', @@ -31,7 +31,7 @@ export const route: Route = { radar: [ { source: ['30secondsofcode.org/:category/:subCategory/', '30secondsofcode.org/:category/'], - target: '/:category/:subCategory', + target: '/category/:category/:subCategory', }, ], name: 'Category and Subcategory', diff --git a/lib/routes/30secondsofcode/namespace.ts b/lib/routes/30secondsofcode/namespace.ts index 40839e5fa75321..b1b8b59d06ccc4 100644 --- a/lib/routes/30secondsofcode/namespace.ts +++ b/lib/routes/30secondsofcode/namespace.ts @@ -2,6 +2,6 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { name: '30 Seconds of code', - url: '30secondsofcode.org', + url: 'www.30secondsofcode.org', lang: 'en', }; diff --git a/lib/routes/30secondsofcode/new-and-popular.ts b/lib/routes/30secondsofcode/new-and-popular.ts index 985b1c40088f18..27f174bd80ce53 100644 --- a/lib/routes/30secondsofcode/new-and-popular.ts +++ b/lib/routes/30secondsofcode/new-and-popular.ts @@ -4,9 +4,9 @@ import { processList, rootUrl } from './utils'; import ofetch from '@/utils/ofetch'; export const route: Route = { - path: '/', - categories: ['programming', 'popular'], - example: '/', + path: '/latest', + categories: ['programming'], + example: '/latest', features: { requireConfig: false, requirePuppeteer: false, @@ -18,7 +18,7 @@ export const route: Route = { radar: [ { source: ['30secondsofcode.org'], - target: '/', + target: '/latest', }, ], name: 'New & Popular Snippets', diff --git a/lib/routes/30secondsofcode/utils.ts b/lib/routes/30secondsofcode/utils.ts index acb3484ad92ca6..9ffbab8b5045b9 100644 --- a/lib/routes/30secondsofcode/utils.ts +++ b/lib/routes/30secondsofcode/utils.ts @@ -37,7 +37,7 @@ async function processItem({ link: articleLink, date }) { } }); const image = article.find('img').attr('src'); - const description = article.clone().find('script').remove().end().html(); + const description = article.clone().find('h1, script').remove().end().html(); return { title,