2024-03-01 20:42:21 +01:00
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
2025-11-03 22:24:31 +01:00
|
|
|
import defaultSubplebbitsData from '../data/default-subplebbits.json';
|
2024-03-01 20:42:21 +01:00
|
|
|
|
2024-05-17 14:55:41 +02:00
|
|
|
export interface MultisubMetadata {
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
createdAt: number;
|
|
|
|
|
updatedAt: number;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-04 16:56:49 +02:00
|
|
|
export interface MultisubSubplebbit {
|
2024-03-01 20:42:21 +01:00
|
|
|
title?: string;
|
|
|
|
|
address: string;
|
2025-11-03 22:06:18 +01:00
|
|
|
nsfw?: boolean;
|
2024-03-01 20:42:21 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-03 22:24:31 +01:00
|
|
|
export interface MultisubData {
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
createdAt: number;
|
|
|
|
|
updatedAt: number;
|
|
|
|
|
subplebbits: MultisubSubplebbit[];
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-20 15:51:29 +01:00
|
|
|
export interface DefaultSubplebbitsState {
|
|
|
|
|
subplebbits: MultisubSubplebbit[];
|
|
|
|
|
loading: boolean;
|
|
|
|
|
error: Error | null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 22:24:31 +01:00
|
|
|
const GITHUB_URL = 'https://raw.githubusercontent.com/plebbit/lists/master/5chan-multisub.json';
|
|
|
|
|
const LOCALSTORAGE_KEY = '5chan-subplebbits-cache';
|
|
|
|
|
const LOCALSTORAGE_TIMESTAMP_KEY = '5chan-subplebbits-cache-timestamp';
|
|
|
|
|
const CACHE_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
|
|
|
|
|
|
2024-05-17 14:55:41 +02:00
|
|
|
let cacheSubplebbits: MultisubSubplebbit[] | null = null;
|
|
|
|
|
let cacheMetadata: MultisubMetadata | null = null;
|
2024-03-01 20:42:21 +01:00
|
|
|
|
2025-11-03 22:24:31 +01:00
|
|
|
const getFromLocalStorage = (): MultisubData | null => {
|
|
|
|
|
try {
|
|
|
|
|
const cached = localStorage.getItem(LOCALSTORAGE_KEY);
|
|
|
|
|
const timestamp = localStorage.getItem(LOCALSTORAGE_TIMESTAMP_KEY);
|
|
|
|
|
if (cached && timestamp) {
|
|
|
|
|
const age = Date.now() - parseInt(timestamp, 10);
|
|
|
|
|
if (age < CACHE_MAX_AGE_MS) {
|
|
|
|
|
return JSON.parse(cached);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('Failed to read from localStorage:', e);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const saveToLocalStorage = (data: MultisubData) => {
|
|
|
|
|
try {
|
|
|
|
|
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(data));
|
|
|
|
|
localStorage.setItem(LOCALSTORAGE_TIMESTAMP_KEY, Date.now().toString());
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('Failed to save to localStorage:', e);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchMultisubData = async (): Promise<MultisubData> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(GITHUB_URL);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
// Save successful fetch to localStorage
|
|
|
|
|
saveToLocalStorage(data);
|
|
|
|
|
return data;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('Failed to fetch subplebbits from GitHub, using vendored fallback:', e);
|
|
|
|
|
// Fall back to vendored file
|
|
|
|
|
return defaultSubplebbitsData as MultisubData;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-20 15:15:19 +01:00
|
|
|
export const useDefaultSubplebbits = () => {
|
2025-02-20 15:51:29 +01:00
|
|
|
const [state, setState] = useState<DefaultSubplebbitsState>({
|
|
|
|
|
subplebbits: [],
|
|
|
|
|
loading: true,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
2024-03-01 20:42:21 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-05-17 14:55:41 +02:00
|
|
|
if (cacheSubplebbits) {
|
2025-02-20 15:51:29 +01:00
|
|
|
setState({
|
|
|
|
|
subplebbits: cacheSubplebbits,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
2024-03-01 20:42:21 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2025-02-20 15:51:29 +01:00
|
|
|
|
2024-03-01 20:42:21 +01:00
|
|
|
(async () => {
|
|
|
|
|
try {
|
2025-11-03 22:24:31 +01:00
|
|
|
// Check localStorage first
|
|
|
|
|
const cachedData = getFromLocalStorage();
|
|
|
|
|
if (cachedData) {
|
|
|
|
|
cacheSubplebbits = cachedData.subplebbits;
|
|
|
|
|
setState({
|
|
|
|
|
subplebbits: cachedData.subplebbits,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
|
|
|
|
// Still try to fetch fresh data in background (don't await)
|
|
|
|
|
fetchMultisubData().then((data) => {
|
|
|
|
|
cacheSubplebbits = data.subplebbits;
|
|
|
|
|
setState({
|
|
|
|
|
subplebbits: data.subplebbits,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-20 15:51:29 +01:00
|
|
|
|
2025-11-03 22:24:31 +01:00
|
|
|
// No cache, fetch fresh data
|
|
|
|
|
const multisub = await fetchMultisubData();
|
2025-11-03 22:06:18 +01:00
|
|
|
cacheSubplebbits = multisub.subplebbits;
|
2025-02-20 15:15:19 +01:00
|
|
|
|
2025-02-20 15:51:29 +01:00
|
|
|
setState({
|
2025-11-03 22:06:18 +01:00
|
|
|
subplebbits: multisub.subplebbits,
|
2025-02-20 15:51:29 +01:00
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
2024-03-01 20:42:21 +01:00
|
|
|
} catch (e) {
|
2025-11-03 22:24:31 +01:00
|
|
|
console.warn('Failed to load subplebbits:', e);
|
|
|
|
|
// Fallback to vendored data
|
|
|
|
|
const fallbackData = defaultSubplebbitsData as MultisubData;
|
|
|
|
|
cacheSubplebbits = fallbackData.subplebbits;
|
|
|
|
|
setState({
|
|
|
|
|
subplebbits: fallbackData.subplebbits,
|
2025-02-20 15:51:29 +01:00
|
|
|
loading: false,
|
2025-11-03 22:24:31 +01:00
|
|
|
error: null,
|
|
|
|
|
});
|
2024-03-01 20:42:21 +01:00
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-02-20 15:51:29 +01:00
|
|
|
// To maintain backward compatibility, return the subplebbits array directly
|
|
|
|
|
return cacheSubplebbits || state.subplebbits;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useDefaultSubplebbitsState = () => {
|
|
|
|
|
const [state, setState] = useState<DefaultSubplebbitsState>({
|
|
|
|
|
subplebbits: cacheSubplebbits || [],
|
|
|
|
|
loading: !cacheSubplebbits,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (cacheSubplebbits) {
|
|
|
|
|
setState({
|
|
|
|
|
subplebbits: cacheSubplebbits,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(async () => {
|
|
|
|
|
try {
|
2025-11-03 22:24:31 +01:00
|
|
|
// Check localStorage first
|
|
|
|
|
const cachedData = getFromLocalStorage();
|
|
|
|
|
if (cachedData) {
|
|
|
|
|
cacheSubplebbits = cachedData.subplebbits;
|
|
|
|
|
setState({
|
|
|
|
|
subplebbits: cachedData.subplebbits,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
|
|
|
|
// Still try to fetch fresh data in background (don't await)
|
|
|
|
|
fetchMultisubData().then((data) => {
|
|
|
|
|
cacheSubplebbits = data.subplebbits;
|
|
|
|
|
setState({
|
|
|
|
|
subplebbits: data.subplebbits,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-20 15:51:29 +01:00
|
|
|
|
2025-11-03 22:24:31 +01:00
|
|
|
// No cache, fetch fresh data
|
|
|
|
|
const multisub = await fetchMultisubData();
|
2025-11-03 22:06:18 +01:00
|
|
|
cacheSubplebbits = multisub.subplebbits;
|
|
|
|
|
|
2025-02-20 15:51:29 +01:00
|
|
|
setState({
|
|
|
|
|
subplebbits: multisub.subplebbits,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
2025-11-03 22:24:31 +01:00
|
|
|
console.warn('Failed to load subplebbits:', e);
|
|
|
|
|
// Fallback to vendored data
|
|
|
|
|
const fallbackData = defaultSubplebbitsData as MultisubData;
|
|
|
|
|
cacheSubplebbits = fallbackData.subplebbits;
|
|
|
|
|
setState({
|
|
|
|
|
subplebbits: fallbackData.subplebbits,
|
2025-02-20 15:51:29 +01:00
|
|
|
loading: false,
|
2025-11-03 22:24:31 +01:00
|
|
|
error: null,
|
|
|
|
|
});
|
2025-02-20 15:51:29 +01:00
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return state;
|
2024-03-01 20:42:21 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useDefaultSubplebbitAddresses = () => {
|
|
|
|
|
const defaultSubplebbits = useDefaultSubplebbits();
|
2025-02-03 13:08:24 +01:00
|
|
|
return useMemo(() => defaultSubplebbits.map((subplebbit) => subplebbit.address), [defaultSubplebbits]);
|
2024-03-01 20:42:21 +01:00
|
|
|
};
|
|
|
|
|
|
2024-05-17 14:55:41 +02:00
|
|
|
export const useMultisubMetadata = () => {
|
|
|
|
|
const [metadata, setMetadata] = useState<MultisubMetadata | null>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (cacheMetadata) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
(async () => {
|
|
|
|
|
try {
|
2025-11-03 22:24:31 +01:00
|
|
|
// Check localStorage first
|
|
|
|
|
const cachedData = getFromLocalStorage();
|
|
|
|
|
if (cachedData) {
|
|
|
|
|
const metadata: MultisubMetadata = {
|
|
|
|
|
title: cachedData.title,
|
|
|
|
|
description: cachedData.description,
|
|
|
|
|
createdAt: cachedData.createdAt,
|
|
|
|
|
updatedAt: cachedData.updatedAt,
|
|
|
|
|
};
|
|
|
|
|
cacheMetadata = metadata;
|
|
|
|
|
setMetadata(metadata);
|
|
|
|
|
// Still try to fetch fresh data in background (don't await)
|
|
|
|
|
fetchMultisubData().then((data) => {
|
|
|
|
|
const freshMetadata: MultisubMetadata = {
|
|
|
|
|
title: data.title,
|
|
|
|
|
description: data.description,
|
|
|
|
|
createdAt: data.createdAt,
|
|
|
|
|
updatedAt: data.updatedAt,
|
|
|
|
|
};
|
|
|
|
|
cacheMetadata = freshMetadata;
|
|
|
|
|
setMetadata(freshMetadata);
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No cache, fetch fresh data
|
|
|
|
|
const multisub = await fetchMultisubData();
|
|
|
|
|
const metadata: MultisubMetadata = {
|
|
|
|
|
title: multisub.title,
|
|
|
|
|
description: multisub.description,
|
|
|
|
|
createdAt: multisub.createdAt,
|
|
|
|
|
updatedAt: multisub.updatedAt,
|
|
|
|
|
};
|
2024-05-17 14:55:41 +02:00
|
|
|
cacheMetadata = metadata;
|
|
|
|
|
setMetadata(metadata);
|
|
|
|
|
} catch (e) {
|
2025-11-03 22:24:31 +01:00
|
|
|
console.warn('Failed to load metadata, using vendored fallback:', e);
|
|
|
|
|
// Fallback to vendored data
|
|
|
|
|
const fallbackData = defaultSubplebbitsData as MultisubData;
|
|
|
|
|
const metadata: MultisubMetadata = {
|
|
|
|
|
title: fallbackData.title,
|
|
|
|
|
description: fallbackData.description,
|
|
|
|
|
createdAt: fallbackData.createdAt,
|
|
|
|
|
updatedAt: fallbackData.updatedAt,
|
|
|
|
|
};
|
|
|
|
|
cacheMetadata = metadata;
|
|
|
|
|
setMetadata(metadata);
|
2024-05-17 14:55:41 +02:00
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return cacheMetadata || metadata;
|
|
|
|
|
};
|