diff --git a/.gitea/ISSUE_TEMPLATE.md b/.gitea/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..6e5ba97 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE.md @@ -0,0 +1,21 @@ +## Issue information + +#### 🖥️ Browser + +... + +#### 📱 Device + +... + +#### 📝 Reason + +... + +#### 🔗 URL + +... + +#### 🏷️ Version + +... diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4916515 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ +## Description + +... + +## Browsers + +- [ ] Google Chrome (specify version if checked) +- [ ] Microsoft Edge (specify version if checked) +- [ ] Mozilla Firefox (specify version if checked) +- [ ] Opera (specify version if checked) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 90199ee..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: ['https://paypal.me/wanhose'] diff --git a/README.md b/README.md new file mode 100644 index 0000000..392c727 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +> AN IMPORTANT UPDATE +>

+> On the morning of October 11, 2024, GitHub unexpectedly hid our repository without any prior notification. This sudden action immediately disrupted all of our API services, as they relied on files from the repository that were no longer accessible. Our team reached out to GitHub support to understand the situation, but we have not yet received any response. Given the critical impact on our services and the lack of communication from GitHub, we decided to migrate the entire repository to an alternative platform to ensure continuity and reliability. +>

+> We initially migrated the repository, releases, and open issues (excluding discussions) to GitLab. However, during the migration of issues, GitLab's own spam detection mechanism mistakenly identified its bot activity as spam, leading to our issues being hidden. Despite reaching out to GitLab support, we have not yet received a resolution for this incident. Faced with these ongoing platform limitations and communication delays, we have opted to move forward with self-hosting our own Git system using Gitea to ensure full control over our repository and services. +>

+> We will no longer support platforms that overlook the contributions of our users and the significant work invested over the last five years. Once our GitHub account is reinstated, we will set up a redirect to guide users to our new, self-hosted repository. +>

+> Thank you to our community for your patience. Your contributions remain vital to this project, and we are committed to ensuring its stability and growth in a more secure environment. + +# Cookie Dialog Monster + +Cookie Dialog Monster is a browser extension that hides cookie consent dialogs without changing user preferences. By default, we do NOT accept cookies (except in [a few cases](https://git.wanhose.dev/wanhose/cookie-dialog-monster/src/branch/main/database.json#L248) where the pages do not function without accepting them). You can report broken sites with a single click, which will create an issue in this repository to be fixed promptly. + +## Repositories + +- [API](/wanhose/cookie-dialog-monster/src/branch/main/packages/api) +- [Browser extension](/wanhose/cookie-dialog-monster/src/branch/main/packages/browser-extension) +- [Web](/wanhose/cookie-dialog-monster/src/branch/main/packages/web) diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 803b907..0000000 --- a/docs/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Cookie Monster Dialog - -Cookie Monster Dialog is a browser extension that hides cookie consent dialogs without changing user preferences. By default, we do NOT accept cookies (except in [a few cases](https://github.com/wanhose/cookie-dialog-monster/blob/main/database.json#L248) where the pages do not function without accepting them). You can report broken sites with a single click, which will create an issue in this repository to be fixed promptly. - -## Repositories - -- [API](/packages/api/) -- [Browser extension](/packages/browser-extension/) -- [Web](/packages/web/) diff --git a/docs/pull_request_template.md b/docs/pull_request_template.md deleted file mode 100644 index 587485b..0000000 --- a/docs/pull_request_template.md +++ /dev/null @@ -1,10 +0,0 @@ -## Description - -Give a short description about this pull request. - -## Browsers - -- [ ] Google Chrome (specify version if checked). -- [ ] Microsoft Edge (specify version if checked). -- [ ] Mozilla Firefox (specify version if checked). -- [ ] Opera (specify version if checked). diff --git a/packages/api/.env.example b/packages/api/.env.example index e0915c7..cd2ff63 100644 --- a/packages/api/.env.example +++ b/packages/api/.env.example @@ -1 +1,2 @@ -GITHUB_TOKEN=? +GITEA_RAW=? +GITEA_TOKEN=? diff --git a/packages/api/docs/README.md b/packages/api/README.md similarity index 95% rename from packages/api/docs/README.md rename to packages/api/README.md index 3559ffb..26fc638 100644 --- a/packages/api/docs/README.md +++ b/packages/api/README.md @@ -1,4 +1,4 @@ -# Cookie Monster Dialog API +# Cookie Dialog Monster API ## Installation diff --git a/packages/api/package.json b/packages/api/package.json index 96f04d6..a9d3823 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,7 +12,6 @@ "@fastify/rate-limit": "^9.1.0", "fastify": "^4.26.2", "node-fetch": "^2.7.0", - "octokit": "^3.2.1", "ua-parser-js": "^1.0.37", "yup": "^1.4.0" }, diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 788b2d2..38e7ed8 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -12,7 +12,6 @@ import v4ReportRoutes from 'routes/v4/report'; import v5DataRoutes from 'routes/v5/data'; import v5IssuesRoutes from 'routes/v5/issues'; import v5ReportRoutes from 'routes/v5/report'; -import v5VersionRoutes from 'routes/v5/version'; import environment from 'services/environment'; const server = fastify({ logger: true }); @@ -27,8 +26,6 @@ server.register(cors, { server.register(rateLimit, { global: false, - max: 1, - timeWindow: 30000, }); server.register(v1EntriesRoutes, { prefix: '/rest/v1' }); @@ -42,7 +39,6 @@ server.register(v4ReportRoutes, { prefix: '/rest/v4' }); server.register(v5DataRoutes, { prefix: '/rest/v5' }); server.register(v5IssuesRoutes, { prefix: '/rest/v5' }); server.register(v5ReportRoutes, { prefix: '/rest/v5' }); -server.register(v5VersionRoutes, { prefix: '/rest/v5' }); server.listen({ host: '0.0.0.0', port: environment.port }, (error, address) => { if (error) { diff --git a/packages/api/src/routes/v1/entries.ts b/packages/api/src/routes/v1/entries.ts index 63c2f5c..6cd07d4 100644 --- a/packages/api/src/routes/v1/entries.ts +++ b/packages/api/src/routes/v1/entries.ts @@ -1,15 +1,24 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; +import { RATE_LIMIT_1_PER_HOUR } from 'services/rateLimit'; /** * @deprecated This API route is no longer supported. Please use a newer version */ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { - server.get('/entries/', async (_request, reply) => { - reply.send({ - success: false, - errors: ['This API route is no longer supported. Please use a newer version'], - }); - }); + server.get( + '/entries/', + { + config: { + rateLimit: RATE_LIMIT_1_PER_HOUR, + }, + }, + async (_request, reply) => { + reply.send({ + success: false, + errors: ['This API route is no longer supported. Please use a newer version'], + }); + } + ); done(); }; diff --git a/packages/api/src/routes/v1/report.ts b/packages/api/src/routes/v1/report.ts index 6ec6f57..c76612d 100644 --- a/packages/api/src/routes/v1/report.ts +++ b/packages/api/src/routes/v1/report.ts @@ -1,15 +1,24 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; +import { RATE_LIMIT_1_PER_HOUR } from 'services/rateLimit'; /** * @deprecated This API route is no longer supported. Please use a newer version */ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { - server.post('/report/', {}, async (_request, reply) => { - reply.send({ - success: false, - errors: ['This API route is no longer supported. Please use a newer version'], - }); - }); + server.post( + '/report/', + { + config: { + rateLimit: RATE_LIMIT_1_PER_HOUR, + }, + }, + async (_request, reply) => { + reply.send({ + success: false, + errors: ['This API route is no longer supported. Please use a newer version'], + }); + } + ); done(); }; diff --git a/packages/api/src/routes/v2/data.ts b/packages/api/src/routes/v2/data.ts index fa0087a..bfc2f19 100644 --- a/packages/api/src/routes/v2/data.ts +++ b/packages/api/src/routes/v2/data.ts @@ -2,29 +2,40 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import fetch from 'node-fetch'; import { parseNewFix } from 'services/compatibility'; import environment from 'services/environment'; +import { RATE_LIMIT_10_PER_MIN } from 'services/rateLimit'; export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { - server.get('/data/', async (_request, reply) => { - try { - const url = `${environment.github.files}/database.json`; - const result = await (await fetch(url)).json(); + server.get( + '/data/', + { + config: { + rateLimit: RATE_LIMIT_10_PER_MIN, + }, + }, + async (_request, reply) => { + try { + const url = `${environment.gitea.raw}/database.json`; + const result = await (await fetch(url)).json(); - reply.send({ - data: { - classes: result.tokens.classes, - commonWords: result.commonWords, - elements: result.tokens.selectors, - fixes: result.fixes.map(parseNewFix), - skips: result.skips.domains, - tags: result.skips.tags, - }, - success: true, - }); - } catch (e) { - console.error(e); - reply.send({ success: false }); + reply.send({ + data: { + classes: result.tokens.classes, + commonWords: result.commonWords, + elements: result.tokens.selectors, + fixes: result.fixes.map(parseNewFix), + skips: result.skips.domains, + tags: result.skips.tags, + }, + success: true, + }); + } catch (error) { + reply.send({ + errors: [error.message], + success: false, + }); + } } - }); + ); done(); }; diff --git a/packages/api/src/routes/v2/report.ts b/packages/api/src/routes/v2/report.ts index 140d57c..ceaf2f5 100644 --- a/packages/api/src/routes/v2/report.ts +++ b/packages/api/src/routes/v2/report.ts @@ -1,6 +1,7 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; -import environment from 'services/environment'; -import { octokit } from 'services/octokit'; +import { formatMessage } from 'services/format'; +import { createIssue, createIssueComment, getIssue, updateIssue } from 'services/git'; +import { RATE_LIMIT_1_PER_MIN } from 'services/rateLimit'; import { validatorCompiler } from 'services/validation'; import { UAParser } from 'ua-parser-js'; import * as yup from 'yup'; @@ -22,6 +23,9 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: server.post<{ Body: PostReportBody }>( '/report/', { + config: { + rateLimit: RATE_LIMIT_1_PER_MIN, + }, schema: { body: PostReportBodySchema, }, @@ -29,45 +33,45 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: }, async (request, reply) => { try { - const issues = await octokit.request('GET /repos/{owner}/{repo}/issues', { - owner: environment.github.owner, - repo: environment.github.repo, - }); - const ua = new UAParser(request.body.userAgent ?? '').getResult(); - const url = new URL(request.body.url).hostname - .split('.') - .slice(-3) - .join('.') - .replace('www.', ''); + const { reason, url, userAgent, version } = request.body; + const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); + const issue = await getIssue({ title: hostname }); + const ua = new UAParser(userAgent ?? '').getResult(); - if (issues.data.some((issue) => issue.title.includes(url))) { - throw new Error(); + if (issue) { + if (issue.state === 'closed') { + await updateIssue({ + id: issue.id, + labels: ['bug'], + state: 'open', + }); + } + + await createIssueComment({ + description: formatMessage({ reason, ua, url, version }), + id: issue.id, + }); + + reply.send({ + success: true, + }); + return; } - await octokit.request('POST /repos/{owner}/{repo}/issues', { - assignees: [environment.github.owner], - body: [ - '## Specifications', - '#### Browser', - `${ua.browser.name ? `${ua.browser.name} ${ua.browser.version || ''}` : '-'}`, - '#### Device', - `${ua.device.type && ua.device.vendor ? `${ua.device.vendor} (${ua.device.type})` : '-'}`, - '#### Reason', - request.body.reason ?? '-', - '#### URL', - request.body.url, - '#### Version', - request.body.version, - ].join('\n'), + await createIssue({ + description: formatMessage({ reason, ua, url, version }), labels: ['bug'], - owner: environment.github.owner, - repo: environment.github.repo, - title: url, + title: hostname, }); - reply.send({ success: true }); + reply.send({ + success: true, + }); } catch (error) { - reply.send({ errors: [error.message], success: false }); + reply.send({ + errors: [error.message], + success: false, + }); } } ); diff --git a/packages/api/src/routes/v3/data.ts b/packages/api/src/routes/v3/data.ts index 4dfcaa2..90d2a1a 100644 --- a/packages/api/src/routes/v3/data.ts +++ b/packages/api/src/routes/v3/data.ts @@ -2,27 +2,36 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import fetch from 'node-fetch'; import { parseNewFix } from 'services/compatibility'; import environment from 'services/environment'; +import { RATE_LIMIT_3_PER_MIN } from 'services/rateLimit'; export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { - server.get('/data/', async (_request, reply) => { - try { - const url = `${environment.github.files}/database.json`; - const result = await (await fetch(url)).json(); + server.get( + '/data/', + { + config: { + rateLimit: RATE_LIMIT_3_PER_MIN, + }, + }, + async (_request, reply) => { + try { + const url = `${environment.gitea.raw}/database.json`; + const result = await (await fetch(url)).json(); - reply.send({ - data: { - ...result, - fixes: result.fixes.map(parseNewFix), - }, - success: true, - }); - } catch (error) { - reply.send({ - errors: [error.message], - success: false, - }); + reply.send({ + data: { + ...result, + fixes: result.fixes.map(parseNewFix), + }, + success: true, + }); + } catch (error) { + reply.send({ + errors: [error.message], + success: false, + }); + } } - }); + ); done(); }; diff --git a/packages/api/src/routes/v3/report.ts b/packages/api/src/routes/v3/report.ts index 9fa1c89..dbafd3a 100644 --- a/packages/api/src/routes/v3/report.ts +++ b/packages/api/src/routes/v3/report.ts @@ -1,6 +1,7 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; -import environment from 'services/environment'; -import { octokit } from 'services/octokit'; +import { formatMessage } from 'services/format'; +import { createIssue, createIssueComment, getIssue, updateIssue } from 'services/git'; +import { RATE_LIMIT_1_PER_MIN } from 'services/rateLimit'; import { validatorCompiler } from 'services/validation'; import { UAParser } from 'ua-parser-js'; import * as yup from 'yup'; @@ -22,63 +23,50 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: server.post<{ Body: PostReportBody }>( '/report/', { + config: { + rateLimit: RATE_LIMIT_1_PER_MIN, + }, schema: { body: PostReportBodySchema, }, validatorCompiler, }, async (request, reply) => { - const { url, userAgent } = request.body; - const ua = new UAParser(userAgent ?? '').getResult(); - const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); - const existingIssues = await octokit.request('GET /search/issues', { - per_page: 50, - q: `in:title+is:issue+repo:${environment.github.owner}/${environment.github.repo}+${hostname}`, - }); - const existingIssue = existingIssues.data.items.find( - (issue) => - hostname === issue.title && - (issue.state === 'open' || - (issue.state === 'closed' && issue.labels.some((label) => label.name === 'wontfix'))) - ); - try { - if (existingIssue) { - if (existingIssue.state === 'closed') { - await octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', { - issue_number: existingIssue.number, + const { reason, url, userAgent, version } = request.body; + const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); + const issue = await getIssue({ title: hostname }); + const ua = new UAParser(userAgent ?? '').getResult(); + + if (issue) { + if (issue.state === 'closed') { + await updateIssue({ + id: issue.id, labels: ['bug'], - owner: environment.github.owner, - repo: environment.github.repo, state: 'open', }); } - await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { - body: generateText(request.body, ua), - issue_number: existingIssue.number, - owner: environment.github.owner, - repo: environment.github.repo, + await createIssueComment({ + description: formatMessage({ reason, ua, url, version }), + id: issue.id, }); reply.send({ - data: existingIssue.html_url, + data: issue.html_url, success: true, }); return; } - const response = await octokit.request('POST /repos/{owner}/{repo}/issues', { - assignees: [environment.github.owner], - body: generateText(request.body, ua), + const newIssue = await createIssue({ + description: formatMessage({ reason, ua, url, version }), labels: ['bug'], - owner: environment.github.owner, - repo: environment.github.repo, title: hostname, }); reply.send({ - data: response.data.html_url, + data: newIssue.html_url, success: true, }); } catch (error) { @@ -92,21 +80,3 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: done(); }; - -function generateText(body: PostReportBody, ua: UAParser.IResult): string { - return [ - '## Issue information', - ...(ua.browser.name && ua.browser.version - ? ['#### 🖥️ Browser', `${ua.browser.name} (${ua.browser.version})`] - : []), - ...(ua.device.type && ua.device.vendor - ? ['#### 📱 Device', `${ua.device.vendor} (${ua.device.type})`] - : []), - '#### 📝 Reason', - body.reason, - '#### 🔗 URL', - body.url, - '#### 🏷️ Version', - body.version, - ].join('\n'); -} diff --git a/packages/api/src/routes/v4/data.ts b/packages/api/src/routes/v4/data.ts index d7efbfd..70c7e4f 100644 --- a/packages/api/src/routes/v4/data.ts +++ b/packages/api/src/routes/v4/data.ts @@ -1,28 +1,37 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import fetch from 'node-fetch'; import environment from 'services/environment'; +import { RATE_LIMIT_3_PER_MIN } from 'services/rateLimit'; export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { - server.get('/data/', async (_request, reply) => { - try { - const options = { headers: { 'Cache-Control': 'no-cache' } }; - const url = `${environment.github.files}/database.json`; - const { rules, ...result } = await (await fetch(url, options)).json(); + server.get( + '/data/', + { + config: { + rateLimit: RATE_LIMIT_3_PER_MIN, + }, + }, + async (_request, reply) => { + try { + const options = { headers: { 'Cache-Control': 'no-cache' } }; + const url = `${environment.gitea.raw}/database.json`; + const { rules, ...result } = await (await fetch(url, options)).json(); - reply.send({ - data: { - ...result, - rules: rules.map(toDeclarativeNetRequestRule), - }, - success: true, - }); - } catch (error) { - reply.send({ - errors: [error.message], - success: false, - }); + reply.send({ + data: { + ...result, + rules: rules.map(toDeclarativeNetRequestRule), + }, + success: true, + }); + } catch (error) { + reply.send({ + errors: [error.message], + success: false, + }); + } } - }); + ); done(); }; diff --git a/packages/api/src/routes/v5/issues.ts b/packages/api/src/routes/v5/issues.ts index 2d11f53..0958a90 100644 --- a/packages/api/src/routes/v5/issues.ts +++ b/packages/api/src/routes/v5/issues.ts @@ -1,6 +1,6 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; -import environment from 'services/environment'; -import { octokit } from 'services/octokit'; +import { getIssue } from 'services/git'; +import { RATE_LIMIT_10_PER_MIN } from 'services/rateLimit'; import { validatorCompiler } from 'services/validation'; import * as yup from 'yup'; @@ -14,6 +14,9 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: server.get<{ Params: GetIssuesParams }>( '/issues/:hostname', { + config: { + rateLimit: RATE_LIMIT_10_PER_MIN, + }, schema: { params: GetIssuesParamsSchema, }, @@ -22,30 +25,22 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: async (request, reply) => { try { const { hostname } = request.params; - const existingIssues = await octokit.request('GET /search/issues', { - per_page: 50, - q: `in:title+is:issue+repo:${environment.github.owner}/${environment.github.repo}+${hostname}`, - }); - const existingIssue = existingIssues.data.items.find( - (issue) => - hostname === issue.title && - (issue.state === 'open' || - (issue.state === 'closed' && issue.labels.some((label) => label.name === 'wontfix'))) - ); + const issue = await getIssue({ title: hostname }); - if (existingIssue) { + if ( + issue && + ((issue.state === 'closed' && issue.labels.some((label) => label.name === 'wontfix')) || + issue.state === 'open') + ) { reply.send({ data: { - flags: existingIssue.labels.map((label) => label.name), - url: existingIssue.html_url, + flags: issue.labels.map((label) => label.name), + url: issue.html_url, }, success: true, }); } else { - reply.send({ - data: {}, - success: true, - }); + throw new Error('Failed to find issue'); } } catch (error) { reply.send({ diff --git a/packages/api/src/routes/v5/report.ts b/packages/api/src/routes/v5/report.ts index 5f2362c..2be87e6 100644 --- a/packages/api/src/routes/v5/report.ts +++ b/packages/api/src/routes/v5/report.ts @@ -1,6 +1,7 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; -import environment from 'services/environment'; -import { octokit } from 'services/octokit'; +import { formatMessage } from 'services/format'; +import { createIssue, getIssue, updateIssue } from 'services/git'; +import { RATE_LIMIT_1_PER_MIN } from 'services/rateLimit'; import { validatorCompiler } from 'services/validation'; import { UAParser } from 'ua-parser-js'; import * as yup from 'yup'; @@ -22,35 +23,34 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: server.post<{ Body: PostReportBody }>( '/report/', { + config: { + rateLimit: RATE_LIMIT_1_PER_MIN, + }, schema: { body: PostReportBodySchema, }, validatorCompiler, }, async (request, reply) => { - const { reason, url, userAgent, version } = request.body; - const ua = new UAParser(userAgent ?? '').getResult(); - const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); - const existingIssues = await octokit.request('GET /search/issues', { - per_page: 50, - q: `in:title+is:issue+repo:${environment.github.owner}/${environment.github.repo}+${hostname}`, - }); - const existingIssue = existingIssues.data.items.find((issue) => hostname === issue.title); - try { - if (existingIssue) { - if (existingIssue.labels.some((label) => label.name === 'wontfix')) { + const { reason, url, userAgent, version } = request.body; + const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); + const issue = await getIssue({ title: hostname }); + const ua = new UAParser(userAgent ?? '').getResult(); + + if (issue) { + if (issue.labels.some((label) => label.name === 'wontfix')) { reply.send({ - data: existingIssue.html_url, + data: issue.html_url, errors: ['This issue has been marked as "wontfix" and will not be addressed.'], success: false, }); return; } - if (existingIssue.state === 'open') { + if (issue.state === 'open') { reply.send({ - data: existingIssue.html_url, + data: issue.html_url, errors: [ 'This issue already exists. Please refer to the existing issue for updates.', ], @@ -59,46 +59,27 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: return; } - await octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', { - issue_number: existingIssue.number, + await updateIssue({ + id: issue.id, labels: ['bug'], - owner: environment.github.owner, - repo: environment.github.repo, state: 'open', }); reply.send({ - data: existingIssue.html_url, + data: issue.html_url, success: true, }); return; } - const response = await octokit.request('POST /repos/{owner}/{repo}/issues', { - assignees: [environment.github.owner], - body: [ - '## Issue information', - ...(ua.browser.name && ua.browser.version - ? ['#### 🖥️ Browser', `${ua.browser.name} (${ua.browser.version})`] - : []), - ...(ua.device.type && ua.device.vendor - ? ['#### 📱 Device', `${ua.device.vendor} (${ua.device.type})`] - : []), - '#### 📝 Reason', - reason, - '#### 🔗 URL', - url, - '#### 🏷️ Version', - version, - ].join('\n'), + const newIssue = await createIssue({ + description: formatMessage({ reason, ua, url, version }), labels: ['bug'], - owner: environment.github.owner, - repo: environment.github.repo, title: hostname, }); reply.send({ - data: response.data.html_url, + data: newIssue.html_url, success: true, }); } catch (error) { diff --git a/packages/api/src/routes/v5/version.ts b/packages/api/src/routes/v5/version.ts deleted file mode 100644 index f6b7b51..0000000 --- a/packages/api/src/routes/v5/version.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { FastifyInstance, RouteShorthandOptions } from 'fastify'; -import fetch from 'node-fetch'; -import environment from 'services/environment'; - -export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { - server.get('/version/', async (_request, reply) => { - try { - const options = { headers: { 'Cache-Control': 'no-cache' } }; - const url = `${environment.github.files}/packages/browser-extension/src/manifest.json`; - const { version } = await (await fetch(url, options)).json(); - - reply.send({ - data: { - version, - }, - success: true, - }); - } catch (error) { - reply.send({ - errors: [error.message], - success: false, - }); - } - }); - - done(); -}; diff --git a/packages/api/src/services/environment.ts b/packages/api/src/services/environment.ts index 85e1bf8..d971952 100644 --- a/packages/api/src/services/environment.ts +++ b/packages/api/src/services/environment.ts @@ -1,9 +1,7 @@ export default { - github: { - files: 'https://raw.githubusercontent.com/wanhose/cookie-dialog-monster/main', - owner: 'wanhose', - repo: 'cookie-dialog-monster', - token: process.env.GITHUB_TOKEN ?? '', + gitea: { + raw: process.env.GITEA_RAW ?? '', + token: process.env.GITEA_TOKEN ?? '', }, port: (process.env.PORT ? Number(process.env.PORT) : undefined) ?? 8080, }; diff --git a/packages/api/src/services/format.ts b/packages/api/src/services/format.ts new file mode 100644 index 0000000..703d034 --- /dev/null +++ b/packages/api/src/services/format.ts @@ -0,0 +1,28 @@ +import type { IResult as UAParserResult } from 'ua-parser-js'; + +export function formatMessage(params: FormatMessageParams): string { + const { reason = '-', ua, url, version } = params; + + return [ + '## Issue information', + ...(ua.browser.name && ua.browser.version + ? ['#### 🖥️ Browser', `${ua.browser.name} (${ua.browser.version})`] + : []), + ...(ua.device.type && ua.device.vendor + ? ['#### 📱 Device', `${ua.device.vendor} (${ua.device.type})`] + : []), + '#### 📝 Reason', + reason, + '#### 🔗 URL', + url, + '#### 🏷️ Version', + version, + ].join('\n'); +} + +export interface FormatMessageParams { + readonly reason?: string; + readonly ua: UAParserResult; + readonly url: string; + readonly version: string; +} diff --git a/packages/api/src/services/git.ts b/packages/api/src/services/git.ts new file mode 100644 index 0000000..e2c0d3d --- /dev/null +++ b/packages/api/src/services/git.ts @@ -0,0 +1,150 @@ +import environment from './environment'; + +const API_URL = 'https://git.wanhose.dev/api/v1/repos/wanhose/cookie-dialog-monster'; + +export async function createIssue(params: CreateIssueParams): Promise { + const { description, title } = params; + const body: { [key: string]: number[] | string | string[] } = { + assignees: ['wanhose'], + body: description, + labels: [], + title, + }; + const headers = new Headers({ + Authorization: `token ${environment.gitea.token}`, + 'Content-Type': 'application/json', + }); + + if (params.labels) { + const response = await fetch(`${API_URL}/labels`, { headers }); + const labels = (await response.json()) as readonly Label[]; + + for (const label of labels) { + if (params.labels.includes(label.name)) { + (body.labels as number[]).push(label.id); + } + } + } + + const response = await fetch(`${API_URL}/issues`, { + body: JSON.stringify(body), + headers, + method: 'POST', + }); + const issue = await response.json(); + + return issue as unknown as Issue; +} + +export async function createIssueComment(params: CreateIssueCommentParams): Promise { + const { description, id } = params; + const headers = new Headers({ + Authorization: `token ${environment.gitea.token}`, + 'Content-Type': 'application/json', + }); + + const response = await fetch(`${API_URL}/issues/${id}/comments`, { + body: JSON.stringify({ body: description }), + headers, + method: 'POST', + }); + const issue = await response.json(); + + return issue as unknown as Issue | null; +} + +export async function getIssue(params: GetIssueParams): Promise { + const { labels, state, title } = params; + const headers = new Headers({ + Authorization: `token ${environment.gitea.token}`, + 'Content-Type': 'application/json', + }); + const search = new URLSearchParams({ + q: title, + state: 'all', + type: 'issues', + }); + + if (labels) { + search.append('labels', `${labels}`); + } + + if (state) { + search.append('state', state); + } + + const response = await fetch(`${API_URL}/issues?${search}`, { headers }); + const issues: readonly Issue[] = (await response.json()) as unknown as readonly Issue[]; + + return issues.find((issue) => issue.title === title) || null; +} + +export async function updateIssue(params: UpdateIssueParams): Promise { + const { id, labels, state } = params; + const body: { [key: string]: string } = {}; + const headers = new Headers({ + Authorization: `token ${environment.gitea.token}`, + 'Content-Type': 'application/json', + }); + + if (labels) { + await fetch(`${API_URL}/issues/${id}/labels`, { + headers, + method: 'DELETE', + }); + await fetch(`${API_URL}/issues/${id}/labels`, { + body: JSON.stringify({ labels }), + headers, + method: 'POST', + }); + } + + if (state) { + body['state'] = state; + } + + const response = await fetch(`${API_URL}/issues/${id}`, { + body: JSON.stringify(body), + headers, + method: 'PATCH', + }); + const issue = await response.json(); + + return issue as unknown as Issue | null; +} + +export interface CreateIssueParams { + readonly description: string; + readonly labels?: readonly string[]; + readonly title: string; +} + +export interface CreateIssueCommentParams { + readonly description: string; + readonly id: number; +} + +export interface GetIssueParams { + readonly labels?: readonly string[]; + readonly state?: string; + readonly title: string; +} + +export interface Issue { + readonly html_url: string; + readonly id: number; + readonly labels: readonly Label[]; + readonly state: string; + readonly title: string; +} + +export interface Label { + readonly id: number; + readonly name: string; +} + +export interface UpdateIssueParams { + readonly id: number; + readonly labels?: readonly string[]; + readonly state?: string; +} diff --git a/packages/api/src/services/octokit.ts b/packages/api/src/services/octokit.ts deleted file mode 100644 index c943fad..0000000 --- a/packages/api/src/services/octokit.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Octokit } from 'octokit'; -import environment from './environment'; - -export const octokit = new Octokit({ auth: environment.github.token }); diff --git a/packages/api/src/services/rateLimit.ts b/packages/api/src/services/rateLimit.ts new file mode 100644 index 0000000..afdd954 --- /dev/null +++ b/packages/api/src/services/rateLimit.ts @@ -0,0 +1,19 @@ +export const RATE_LIMIT_1_PER_HOUR = { + max: 1, + timeWindow: '1 hour', +}; + +export const RATE_LIMIT_1_PER_MIN = { + max: 1, + timeWindow: '1 minute', +}; + +export const RATE_LIMIT_10_PER_MIN = { + max: 10, + timeWindow: '1 minute', +}; + +export const RATE_LIMIT_3_PER_MIN = { + max: 3, + timeWindow: '1 minute', +}; diff --git a/packages/browser-extension/docs/README.md b/packages/browser-extension/README.md similarity index 97% rename from packages/browser-extension/docs/README.md rename to packages/browser-extension/README.md index 26d284e..d4194e9 100644 --- a/packages/browser-extension/docs/README.md +++ b/packages/browser-extension/README.md @@ -1,4 +1,4 @@ -# Cookie Monster Dialog Browser Extension +# Cookie Dialog Monster Browser Extension ## Downloads @@ -11,9 +11,9 @@ ## Compatibility -- All browsers based on Chromium 124+ (Blisk, Brave, Colibri, Epic Browser, Iron Browser, Vivaldi and many more) -- Google Chrome 124+ -- Microsoft Edge 124+ +- All browsers based on Chromium 127+ (Blisk, Brave, Colibri, Epic Browser, Iron Browser, Vivaldi and many more) +- Google Chrome 127+ +- Microsoft Edge 127+ - Mozilla Firefox 126+ - Mozilla Firefox Mobile 126+ diff --git a/packages/web/src/index.html b/packages/web/src/index.html index 259d5df..1b317b1 100644 --- a/packages/web/src/index.html +++ b/packages/web/src/index.html @@ -40,6 +40,48 @@
+