tv / utils /database.js
MineRoma's picture
Upload 9 files
1ae753d verified
Raw History Blame Contribute Delete
10.6 kB
const axios = require('axios');
const crypto = require('./crypto');
let GIST_ID = process.env.GIST_ID;
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
if (!GITHUB_TOKEN) {
console.error('❌ GITHUB_TOKEN not set!');
}
let GIST_API = GIST_ID ? `https://api.github.com/gists/${GIST_ID}` : null;
// Cache
let usersCache = null;
let scheduleCache = null;
let cacheTime = 0;
const CACHE_DURATION = 60000; // 1 minute
let gistInitialized = false;
// Создание нового Gist с дефолтными данными
async function createDefaultGist() {
if (!GITHUB_TOKEN) {
console.error('Cannot create Gist without GITHUB_TOKEN');
return null;
}
try {
console.log('📝 Creating new Gist with default data...');
const defaultData = {
users: [],
schedule: [
{
channelId: 'TV-1',
channelName: 'TV-1',
programs: [
{
time: '08:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 1-4',
file: 'nu-pogodi-1-4.mp4',
description: 'Классика советской анимации'
},
{
time: '10:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 5-8',
file: 'nu-pogodi-5-8.mp4',
description: 'Продолжение приключений'
},
{
time: '12:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 9-12',
file: 'nu-pogodi-9-12.mp4',
description: 'Новые серии'
},
{
time: '14:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 13-16',
file: 'nu-pogodi-13-16.mp4',
description: 'Финальные серии'
},
{
time: '16:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 1-4 (повтор)',
file: 'nu-pogodi-1-4.mp4',
description: 'Повтор'
}
]
}
]
};
const response = await axios.post('https://api.github.com/gists', {
description: 'V-OTV Database (users + schedule)',
public: false,
files: {
'users.json': {
content: JSON.stringify(defaultData.users, null, 2)
},
'schedule.json': {
content: JSON.stringify(defaultData.schedule, null, 2)
}
}
}, {
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json'
}
});
GIST_ID = response.data.id;
GIST_API = `https://api.github.com/gists/${GIST_ID}`;
console.log(`✅ Gist created successfully! ID: ${GIST_ID}`);
console.log(`⚠️ Add this to your HF Secrets: GIST_ID=${GIST_ID}`);
return defaultData;
} catch (error) {
console.error('Failed to create Gist:', error.message);
return null;
}
}
// Инициализация Gist (создание если не существует)
async function initializeGist() {
if (gistInitialized) return true;
if (!GIST_ID && GITHUB_TOKEN) {
console.log('📋 No GIST_ID provided, creating new Gist...');
const data = await createDefaultGist();
if (data) {
usersCache = data.users;
scheduleCache = data.schedule;
cacheTime = Date.now();
gistInitialized = true;
return true;
}
return false;
}
if (!GIST_ID || !GITHUB_TOKEN) {
console.warn('⚠️ GIST_ID or GITHUB_TOKEN not set. Using local fallback.');
gistInitialized = true;
return false;
}
// Проверка существования Gist
try {
await axios.get(GIST_API, {
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json'
}
});
gistInitialized = true;
return true;
} catch (error) {
if (error.response && error.response.status === 404) {
console.log('📋 Gist not found, creating new one...');
const data = await createDefaultGist();
if (data) {
usersCache = data.users;
scheduleCache = data.schedule;
cacheTime = Date.now();
gistInitialized = true;
return true;
}
}
console.error('Gist initialization error:', error.message);
return false;
}
}
// Get Gist data
async function getGistData() {
await initializeGist();
if (!GIST_ID || !GITHUB_TOKEN) {
return getLocalData();
}
try {
const response = await axios.get(GIST_API, {
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json'
}
});
const files = response.data.files;
let users = [];
let schedule = [];
// Загрузка и расшифровка пользователей
if (files['users.json']) {
const encryptedUsers = JSON.parse(files['users.json'].content);
users = crypto.decryptUsers(encryptedUsers);
}
// Загрузка расписания (не шифруется)
if (files['schedule.json']) {
schedule = JSON.parse(files['schedule.json'].content);
}
return { users, schedule };
} catch (error) {
console.error('Gist fetch error:', error.message);
return getLocalData();
}
}
// Update Gist data
async function updateGistData(data) {
await initializeGist();
if (!GIST_ID || !GITHUB_TOKEN) {
return saveLocalData(data);
}
try {
const files = {};
if (data.users) {
// Шифрование пользователей перед сохранением
const encryptedUsers = crypto.encryptUsers(data.users);
files['users.json'] = {
content: JSON.stringify(encryptedUsers, null, 2)
};
}
if (data.schedule) {
// Расписание не шифруется
files['schedule.json'] = {
content: JSON.stringify(data.schedule, null, 2)
};
}
await axios.patch(GIST_API, { files }, {
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json'
}
});
// Clear cache
usersCache = null;
scheduleCache = null;
cacheTime = 0;
return true;
} catch (error) {
console.error('Gist update error:', error.message);
return false;
}
}
// Local fallback
function getLocalData() {
return {
users: [],
schedule: [
{
channelId: 'TV-1',
channelName: 'TV-1',
programs: [
{
time: '08:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 1-4',
file: 'nu-pogodi-1-4.mp4',
description: 'Классика советской анимации'
},
{
time: '10:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 5-8',
file: 'nu-pogodi-5-8.mp4',
description: 'Продолжение приключений'
},
{
time: '12:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 9-12',
file: 'nu-pogodi-9-12.mp4',
description: 'Новые серии'
},
{
time: '14:00', // Время в МСК (UTC+3)
name: 'Ну, погоди! — Выпуск 13-16',
file: 'nu-pogodi-13-16.mp4',
description: 'Финальные серии'
}
]
}
]
};
}
function saveLocalData(data) {
// In production with Gist, this won't be used
console.log('Local save (no-op in production)');
return true;
}
// Public API
async function getUsers() {
const now = Date.now();
if (usersCache && (now - cacheTime) < CACHE_DURATION) {
return usersCache;
}
const data = await getGistData();
usersCache = data.users;
cacheTime = now;
return usersCache;
}
async function saveUsers(users) {
usersCache = users;
return await updateGistData({ users });
}
async function getSchedule() {
const now = Date.now();
if (scheduleCache && (now - cacheTime) < CACHE_DURATION) {
return scheduleCache;
}
const data = await getGistData();
scheduleCache = data.schedule;
cacheTime = now;
return scheduleCache;
}
async function saveSchedule(schedule) {
scheduleCache = schedule;
return await updateGistData({ schedule });
}
module.exports = {
getUsers,
saveUsers,
getSchedule,
saveSchedule,
initializeGist
};