Compare commits
7 Commits
fix-librar
...
preview-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2a4b70ea0 | ||
|
|
62dbde448c | ||
|
|
0116c13e06 | ||
|
|
c96ca6742e | ||
|
|
c80d9a853a | ||
|
|
6cea8bba59 | ||
|
|
2be9c7dcc1 |
@@ -7,9 +7,10 @@ type RateLimiteState<T extends (...args: Parameters<T>) => Promise<U>, U> = {
|
|||||||
queue: {
|
queue: {
|
||||||
args: Parameters<T>;
|
args: Parameters<T>;
|
||||||
resolve: (value: U) => void;
|
resolve: (value: U) => void;
|
||||||
|
reject: (reason?: unknown) => void;
|
||||||
}[];
|
}[];
|
||||||
activeRequests: number;
|
lastTimestamps: number[];
|
||||||
timer: NodeJS.Timeout | null;
|
timeout: ReturnType<typeof setTimeout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const rateLimitById: Record<string, unknown> = {};
|
const rateLimitById: Record<string, unknown> = {};
|
||||||
@@ -27,46 +28,40 @@ export default function rateLimit<
|
|||||||
>(fn: T, options: RateLimitOptions): (...args: Parameters<T>) => Promise<U> {
|
>(fn: T, options: RateLimitOptions): (...args: Parameters<T>) => Promise<U> {
|
||||||
const state: RateLimiteState<T, U> = (rateLimitById[
|
const state: RateLimiteState<T, U> = (rateLimitById[
|
||||||
options.id || ''
|
options.id || ''
|
||||||
] as RateLimiteState<T, U>) || { queue: [], activeRequests: 0, timer: null };
|
] as RateLimiteState<T, U>) || { queue: [], lastTimestamps: [] };
|
||||||
if (options.id) {
|
if (options.id) {
|
||||||
rateLimitById[options.id] = state;
|
rateLimitById[options.id] = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const processQueue = () => {
|
const processQueue = () => {
|
||||||
if (state.queue.length === 0) {
|
// remove old timestamps
|
||||||
if (state.timer) {
|
state.lastTimestamps = state.lastTimestamps.filter(
|
||||||
clearInterval(state.timer);
|
(timestamp) => Date.now() - timestamp < 1000
|
||||||
state.timer = null;
|
);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (state.activeRequests < options.maxRPS) {
|
if (state.lastTimestamps.length < options.maxRPS) {
|
||||||
state.activeRequests++;
|
// process requests if RPS not exceeded
|
||||||
const item = state.queue.shift();
|
const item = state.queue.shift();
|
||||||
if (!item) break;
|
if (!item) return;
|
||||||
const { args, resolve } = item;
|
state.lastTimestamps.push(Date.now());
|
||||||
|
const { args, resolve, reject } = item;
|
||||||
fn(...args)
|
fn(...args)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.finally(() => {
|
.catch(reject);
|
||||||
state.activeRequests--;
|
processQueue();
|
||||||
if (state.queue.length > 0) {
|
} else {
|
||||||
if (!state.timer) {
|
// rerun once the oldest item in queue is older than 1s
|
||||||
state.timer = setInterval(processQueue, 1000);
|
if (state.timeout) clearTimeout(state.timeout);
|
||||||
}
|
state.timeout = setTimeout(
|
||||||
} else {
|
processQueue,
|
||||||
if (state.timer) {
|
1000 - (Date.now() - state.lastTimestamps[0])
|
||||||
clearInterval(state.timer);
|
);
|
||||||
state.timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (...args: Parameters<T>): Promise<U> => {
|
return (...args: Parameters<T>): Promise<U> => {
|
||||||
return new Promise<U>((resolve) => {
|
return new Promise<U>((resolve, reject) => {
|
||||||
state.queue.push({ args, resolve });
|
state.queue.push({ args, resolve, reject });
|
||||||
processQueue();
|
processQueue();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const DiscoverTvNetwork = () => {
|
|||||||
{firstResultData?.network.logoPath ? (
|
{firstResultData?.network.logoPath ? (
|
||||||
<div className="mb-6 flex justify-center">
|
<div className="mb-6 flex justify-center">
|
||||||
<Image
|
<Image
|
||||||
src={`//image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.network.logoPath}`}
|
src={`https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.network.logoPath}`}
|
||||||
alt={firstResultData.network.name}
|
alt={firstResultData.network.name}
|
||||||
className="max-h-24 sm:max-h-32"
|
className="max-h-24 sm:max-h-32"
|
||||||
fill
|
fill
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const DiscoverMovieStudio = () => {
|
|||||||
{firstResultData?.studio.logoPath ? (
|
{firstResultData?.studio.logoPath ? (
|
||||||
<div className="mb-6 flex justify-center">
|
<div className="mb-6 flex justify-center">
|
||||||
<Image
|
<Image
|
||||||
src={`//image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.studio.logoPath}`}
|
src={`https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.studio.logoPath}`}
|
||||||
alt={firstResultData.studio.name}
|
alt={firstResultData.studio.name}
|
||||||
className="max-h-24 sm:max-h-32"
|
className="max-h-24 sm:max-h-32"
|
||||||
fill
|
fill
|
||||||
|
|||||||
@@ -181,6 +181,9 @@ const IssueComment = ({
|
|||||||
`/api/v1/issueComment/${comment.id}`,
|
`/api/v1/issueComment/${comment.id}`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
body: JSON.stringify({ message: values.newMessage }),
|
body: JSON.stringify({ message: values.newMessage }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ const IssueDetails = () => {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/issueComment/${firstComment.id}`, {
|
const res = await fetch(`/api/v1/issueComment/${firstComment.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
body: JSON.stringify({ message: newMessage }),
|
body: JSON.stringify({ message: newMessage }),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error();
|
if (!res.ok) throw new Error();
|
||||||
@@ -501,6 +504,9 @@ const IssueDetails = () => {
|
|||||||
`/api/v1/issue/${issueData?.id}/comment`,
|
`/api/v1/issue/${issueData?.id}/comment`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
body: JSON.stringify({ message: values.message }),
|
body: JSON.stringify({ message: values.message }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ const AddEmailModal: React.FC<AddEmailModalProps> = ({
|
|||||||
try {
|
try {
|
||||||
const res = await fetch('/api/v1/auth/jellyfin', {
|
const res = await fetch('/api/v1/auth/jellyfin', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ const Login = () => {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch('/api/v1/auth/plex', {
|
const res = await fetch('/api/v1/auth/plex', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
body: JSON.stringify({ authToken }),
|
body: JSON.stringify({ authToken }),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error();
|
if (!res.ok) throw new Error();
|
||||||
|
|||||||
@@ -57,44 +57,48 @@ const ShowMoreCard = ({ url, posters }: ShowMoreCardProps) => {
|
|||||||
>
|
>
|
||||||
<div style={{ paddingBottom: '150%' }}>
|
<div style={{ paddingBottom: '150%' }}>
|
||||||
<div className="absolute inset-0 flex h-full w-full flex-col items-center p-2">
|
<div className="absolute inset-0 flex h-full w-full flex-col items-center p-2">
|
||||||
<div className="relative z-10 flex h-full flex-wrap items-center justify-center opacity-30">
|
<div className="relative z-10 grid h-full w-full grid-cols-2 items-center justify-center gap-2 opacity-30">
|
||||||
{posters[0] && (
|
{posters[0] && (
|
||||||
<div className="w-1/2 p-1">
|
<div className="">
|
||||||
<Image
|
<Image
|
||||||
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[0]}`}
|
src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[0]}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-full rounded-md"
|
className="rounded-md"
|
||||||
fill
|
width={300}
|
||||||
|
height={450}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{posters[1] && (
|
{posters[1] && (
|
||||||
<div className="w-1/2 p-1">
|
<div className="">
|
||||||
<Image
|
<Image
|
||||||
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[1]}`}
|
src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[1]}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-full rounded-md"
|
className="rounded-md"
|
||||||
fill
|
width={300}
|
||||||
|
height={450}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{posters[2] && (
|
{posters[2] && (
|
||||||
<div className="w-1/2 p-1">
|
<div className="">
|
||||||
<Image
|
<Image
|
||||||
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[2]}`}
|
src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[2]}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-full rounded-md"
|
className="rounded-md"
|
||||||
fill
|
width={300}
|
||||||
|
height={450}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{posters[3] && (
|
{posters[3] && (
|
||||||
<div className="w-1/2 p-1">
|
<div className="">
|
||||||
<Image
|
<Image
|
||||||
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[3]}`}
|
src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[3]}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-full rounded-md"
|
className="rounded-md"
|
||||||
fill
|
width={300}
|
||||||
|
height={450}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
|||||||
|
|
||||||
const searchParams = new URLSearchParams(params.enable ? params : {});
|
const searchParams = new URLSearchParams(params.enable ? params : {});
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`/api/v1/settings/jellyfin/library?${searchParams.toString}`
|
`/api/v1/settings/jellyfin/library?${searchParams.toString()}`
|
||||||
);
|
);
|
||||||
if (!res.ok) throw new Error();
|
if (!res.ok) throw new Error();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user