add kutt.it shortlink provider

This commit is contained in:
Renan Bernordi 2025-06-10 01:13:25 -03:00
parent b06ccafa52
commit 20eb1dfe88
3 changed files with 134 additions and 0 deletions

View File

@ -104,3 +104,9 @@ POSTIZ_OAUTH_CLIENT_SECRET=""
# DUB_TOKEN="" # Your self-hosted Dub API token
# DUB_API_ENDPOINT="https://api.dub.co" # Your self-hosted Dub API endpoint
# DUB_SHORT_LINK_DOMAIN="dub.sh" # Your self-hosted Dub domain
# SHORT_IO_SECRET_KEY="" # Your Short.io API secret key
# KUTT_API_KEY="" # Your Kutt.it API key
# KUTT_API_ENDPOINT="https://kutt.it/api/v2" # Your self-hosted Kutt API endpoint
# KUTT_SHORT_LINK_DOMAIN="kutt.it" # Your self-hosted Kutt domain

View File

@ -0,0 +1,123 @@
import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';
const KUTT_API_ENDPOINT = process.env.KUTT_API_ENDPOINT || 'https://kutt.it/api/v2';
const KUTT_SHORT_LINK_DOMAIN = process.env.KUTT_SHORT_LINK_DOMAIN || 'kutt.it';
const getOptions = () => ({
headers: {
'X-API-Key': process.env.KUTT_API_KEY,
'Content-Type': 'application/json',
},
});
export class Kutt implements ShortLinking {
shortLinkDomain = KUTT_SHORT_LINK_DOMAIN;
async linksStatistics(links: string[]) {
return Promise.all(
links.map(async (link) => {
const linkId = link.split('/').pop();
try {
const response = await fetch(
`${KUTT_API_ENDPOINT}/links/${linkId}/stats`,
getOptions()
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return {
short: link,
original: data.address || '',
clicks: data.lastDay?.stats?.reduce((total: number, stat: any) => total + stat, 0)?.toString() || '0',
};
} catch (error) {
return {
short: link,
original: '',
clicks: '0',
};
}
})
);
}
async convertLinkToShortLink(id: string, link: string) {
try {
const response = await fetch(`${KUTT_API_ENDPOINT}/links`, {
...getOptions(),
method: 'POST',
body: JSON.stringify({
target: link,
domain: this.shortLinkDomain,
reuse: false,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.link;
} catch (error) {
throw new Error(`Failed to create short link: ${error}`);
}
}
async convertShortLinkToLink(shortLink: string) {
const linkId = shortLink.split('/').pop();
try {
const response = await fetch(
`${KUTT_API_ENDPOINT}/links/${linkId}/stats`,
getOptions()
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.address || '';
} catch (error) {
throw new Error(`Failed to get original link: ${error}`);
}
}
async getAllLinksStatistics(
id: string,
page = 1
): Promise<{ short: string; original: string; clicks: string }[]> {
try {
const response = await fetch(
`${KUTT_API_ENDPOINT}/links?limit=100&skip=${(page - 1) * 100}`,
getOptions()
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const mapLinks = data.data?.map((link: any) => ({
short: link.link,
original: link.address,
clicks: link.visit_count?.toString() || '0',
})) || [];
if (mapLinks.length < 100) {
return mapLinks;
}
return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];
} catch (error) {
return [];
}
}
}

View File

@ -3,6 +3,7 @@ import { Empty } from '@gitroom/nestjs-libraries/short-linking/providers/empty';
import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';
import { Injectable } from '@nestjs/common';
import { ShortIo } from './providers/short.io';
import { Kutt } from './providers/kutt';
const getProvider = (): ShortLinking => {
if (process.env.DUB_TOKEN) {
@ -13,6 +14,10 @@ const getProvider = (): ShortLinking => {
return new ShortIo();
}
if (process.env.KUTT_API_KEY) {
return new Kutt();
}
return new Empty();
};