diff --git a/client.js b/client.js
index d92d1d8..f2c5de7 100644
--- a/client.js
+++ b/client.js
@@ -2239,6 +2239,12 @@ try {
// 4. Global Keydown Listener (Screen-Reader & Turkish Q Optimized)
window.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
+ var quotaModal = document.getElementById('yaktube-quota-modal');
+ if (quotaModal) {
+ quotaModal.remove();
+ announce('Kota uyarısı penceresi kapatıldı.');
+ return;
+ }
var sleepModal = document.getElementById('yaktube-sleep-modal');
if (sleepModal) {
sleepModal.remove();
@@ -2865,85 +2871,314 @@ try {
} catch (e) {}
}
- function handleImport(url, btn, videoTitle) {
- btn.disabled = true;
- btn.innerHTML = '⏳ Aktarılıyor...';
- showModal(
- '[1/2] 📥 Video İndiriliyor...',
- (videoTitle ? '"' + videoTitle + '" ' : 'Video ') + "YouTube'dan sunucunuza aktarılıyor. Lütfen bekleyin..."
- );
+ // --- Guest Video Import Quota & PeerTube Login Redirect Engine ---
+ var cachedGuestImportLimit = null;
- fetch('/api-custom/ondemand-import', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ targetUrl: url, title: videoTitle })
- })
- .then(function (res) {
- return res.json();
+ function fetchGuestImportLimit() {
+ if (cachedGuestImportLimit !== null) {
+ return Promise.resolve(cachedGuestImportLimit);
+ }
+ return fetch('/api-custom/quota-config')
+ .then(function (r) {
+ return r.json();
})
- .then(function (data) {
- if (!data || !data.ok) {
- throw new Error(data && data.error ? data.error : 'İçe aktarma başlatılamadı.');
+ .then(function (d) {
+ if (d && typeof d.guestImportLimit === 'number') {
+ cachedGuestImportLimit = d.guestImportLimit;
+ return cachedGuestImportLimit;
}
+ return 5;
+ })
+ .catch(function () {
+ return fetch('/plugins/yaktube-ondemand/router/quota-config')
+ .then(function (r2) {
+ return r2.json();
+ })
+ .then(function (d2) {
+ if (d2 && typeof d2.guestImportLimit === 'number') {
+ cachedGuestImportLimit = d2.guestImportLimit;
+ return cachedGuestImportLimit;
+ }
+ return 5;
+ })
+ .catch(function () {
+ return 5;
+ });
+ });
+ }
- var videoId = data.video.id;
- var videoUuid = data.video.uuid;
+ // Pre-fetch guest import quota setting quietly on startup
+ setTimeout(function () {
+ fetchGuestImportLimit();
+ }, 600);
- var attempts = 0;
- var pollInterval = setInterval(function () {
- attempts++;
- fetch('/api-custom/status/' + videoId)
- .then(function (sRes) {
- return sRes.json();
- })
- .then(function (sData) {
- if (sData.isImporting) {
- showModal('[1/2] 📥 Video İndiriliyor...', 'Video sunucuya indiriliyor (' + attempts * 2 + ' sn)...');
- } else if (sData.isTranscoding) {
- showModal(
- '[2/2] ⚙️ Oynatıcı Hazırlanıyor...',
- 'Video işleniyor ve yüksek kalite akış hazırlanıyor...'
- );
- }
+ function getGuestImportCount() {
+ var lsCount = 0;
+ var cookieCount = 0;
+ try {
+ lsCount = parseInt(localStorage.getItem('yaktube_guest_import_count') || '0', 10);
+ if (isNaN(lsCount) || lsCount < 0) lsCount = 0;
+ } catch (e) {}
+ try {
+ var match = document.cookie.match(/(?:^|;\s*)yaktube_guest_imports=(\d+)/);
+ if (match && match[1]) {
+ cookieCount = parseInt(match[1], 10);
+ if (isNaN(cookieCount) || cookieCount < 0) cookieCount = 0;
+ }
+ } catch (e) {}
+ var maxCount = Math.max(lsCount, cookieCount);
+ // Keep localStorage and cookie synchronized so refreshing cannot reset it
+ if (maxCount > 0) {
+ try {
+ localStorage.setItem('yaktube_guest_import_count', String(maxCount));
+ } catch (e) {}
+ try {
+ document.cookie = 'yaktube_guest_imports=' + maxCount + '; path=/; max-age=31536000; SameSite=Lax';
+ } catch (e) {}
+ }
+ return maxCount;
+ }
- if (sData.isPublished || (sData.hasFiles && attempts >= 8)) {
- clearInterval(pollInterval);
- showModal('🎉 Video Hazır!', 'Video kütüphanenize eklendi. Oynatıcı açılıyor...');
- setTimeout(function () {
- window.location.href = '/videos/watch/' + (sData.uuid || videoUuid);
- }, 600);
- } else if (sData.isFailed) {
- clearInterval(pollInterval);
- hideModal();
- btn.disabled = false;
- btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
- alert('Video indirme sırasında bir hata oluştu.');
- announce('Video indirme sırasında bir hata oluştu.', true);
- }
- })
- .catch(function (e) {
- console.error('[YakTube] Status poll error:', e);
- });
+ function incrementGuestImportCount() {
+ var nextCount = getGuestImportCount() + 1;
+ try {
+ localStorage.setItem('yaktube_guest_import_count', String(nextCount));
+ } catch (e) {}
+ try {
+ document.cookie = 'yaktube_guest_imports=' + nextCount + '; path=/; max-age=31536000; SameSite=Lax';
+ } catch (e) {}
+ return nextCount;
+ }
- if (attempts > 35) {
- clearInterval(pollInterval);
+ function redirectToPeerTubeLogin() {
+ try {
+ // Save current URL in sessionStorage just as PeerTube's LoginComponent does
+ sessionStorage.setItem('redirect-url-after-login', window.location.pathname + window.location.search);
+ } catch (e) {}
+
+ // 1. Check PeerTube's native login link in header (handles redirectOnSingleExternalAuth / YakNet SSO automatically)
+ var loginLink = document.querySelector(
+ 'my-login-link a.login-button-link, my-login-link a[href], a.login-button-link'
+ );
+ if (loginLink) {
+ var href = loginLink.getAttribute('href');
+ if (href && href !== '#' && !href.startsWith('javascript:')) {
+ window.location.href = href;
+ return;
+ }
+ loginLink.click();
+ return;
+ }
+
+ // 2. Check window.PeertubeServerConfig for single external auth (PeerTube native getDefaultLoginHref logic)
+ try {
+ var cfg = window.PeertubeServerConfig;
+ if (
+ cfg &&
+ cfg.client &&
+ cfg.client.menu &&
+ cfg.client.menu.login &&
+ cfg.client.menu.login.redirectOnSingleExternalAuth === true &&
+ cfg.plugin &&
+ Array.isArray(cfg.plugin.registeredExternalAuths) &&
+ cfg.plugin.registeredExternalAuths.length === 1
+ ) {
+ var ext = cfg.plugin.registeredExternalAuths[0];
+ var npmName = ext.npmName || 'peertube-plugin-' + ext.name;
+ window.location.href = '/plugins/' + ext.name + '/' + ext.version + '/auth/' + ext.authName;
+ return;
+ }
+ } catch (e) {}
+
+ // 3. Standard PeerTube login route fallback
+ window.location.href = '/login';
+ }
+
+ function showQuotaExceededModal(limitVal) {
+ hideModal();
+ var existing = document.getElementById('yaktube-quota-modal');
+ if (existing) existing.remove();
+
+ var modal = document.createElement('div');
+ modal.id = 'yaktube-quota-modal';
+ modal.className = 'yaktube-modal-overlay';
+ modal.setAttribute('role', 'dialog');
+ modal.setAttribute('aria-modal', 'true');
+ modal.setAttribute('aria-labelledby', 'yaktube-quota-modal-title');
+ modal.setAttribute('aria-describedby', 'yaktube-quota-modal-desc');
+
+ var displayLimit = limitVal || cachedGuestImportLimit || getGuestImportCount() || 5;
+ var instanceName = escapeHtml(getInstanceName());
+
+ modal.innerHTML =
+ '
' +
+ '
🔒
' +
+ '
Kota Doldu
' +
+ '
' +
+ 'Misafir kullanıcı olarak ücretsiz ' +
+ displayLimit +
+ ' video indirme sınırına ulaştınız.
' +
+ 'Sınırsız video aktarmaya ve izlemeye devam etmek için lütfen giriş yapın.' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
';
+
+ document.body.appendChild(modal);
+
+ var loginBtn = document.getElementById('yaktube-quota-login-btn');
+ var closeBtn = document.getElementById('yaktube-quota-close-btn');
+
+ if (loginBtn) {
+ loginBtn.addEventListener('click', function () {
+ modal.remove();
+ redirectToPeerTubeLogin();
+ });
+ setTimeout(function () {
+ loginBtn.focus();
+ }, 50);
+ }
+
+ if (closeBtn) {
+ closeBtn.addEventListener('click', function () {
+ modal.remove();
+ announce('Kota uyarısı kapatıldı.');
+ });
+ }
+
+ modal.addEventListener('click', function (e) {
+ if (e.target === modal) {
+ modal.remove();
+ }
+ });
+
+ announce('Kota Doldu. Misafir video indirme sınırına ulaştınız. Devam etmek için lütfen giriş yapın.', true);
+ }
+
+ function handleImport(url, btn, videoTitle) {
+ var loggedIn = isUserLoggedIn();
+ var currentGuestCount = getGuestImportCount();
+
+ fetchGuestImportLimit().then(function (limit) {
+ if (!loggedIn && limit > 0 && currentGuestCount >= limit) {
+ showQuotaExceededModal(limit);
+ return;
+ }
+
+ btn.disabled = true;
+ btn.innerHTML = '⏳ Aktarılıyor...';
+ showModal(
+ '[1/2] 📥 Video İndiriliyor...',
+ (videoTitle ? '"' + videoTitle + '" ' : 'Video ') + "YouTube'dan sunucunuza aktarılıyor. Lütfen bekleyin..."
+ );
+
+ var headers = { 'Content-Type': 'application/json' };
+ try {
+ var token = localStorage.getItem('access_token');
+ if (token) {
+ headers['Authorization'] = 'Bearer ' + token;
+ }
+ } catch (e) {}
+
+ fetch('/api-custom/ondemand-import', {
+ method: 'POST',
+ headers: headers,
+ body: JSON.stringify({
+ targetUrl: url,
+ title: videoTitle,
+ isAuthenticated: loggedIn,
+ guestImportCount: currentGuestCount
+ })
+ })
+ .then(function (res) {
+ return res.json();
+ })
+ .then(function (data) {
+ if (data && data.quotaExceeded) {
hideModal();
btn.disabled = false;
btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
- alert(
- 'İndirme arka planda tamamlanıyor. Ana sayfada veya Videolarım sayfasında kısa süre içinde hazır olacaktır.'
- );
- announce('İndirme arka planda tamamlanıyor. Ana sayfada kısa süre içinde hazır olacaktır.');
+ if (typeof data.limit === 'number' && data.limit > 0) {
+ cachedGuestImportLimit = data.limit;
+ }
+ showQuotaExceededModal(data.limit || limit);
+ return;
}
- }, 1500);
- })
- .catch(function (err) {
- hideModal();
- btn.disabled = false;
- btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
- alert('İndirme hatası: ' + err.message);
- announce('İndirme hatası: ' + err.message, true);
- });
+
+ if (!data || !data.ok) {
+ throw new Error(data && data.error ? data.error : 'İçe aktarma başlatılamadı.');
+ }
+
+ if (!loggedIn) {
+ incrementGuestImportCount();
+ }
+
+ var videoId = data.video.id;
+ var videoUuid = data.video.uuid;
+
+ var attempts = 0;
+ var pollInterval = setInterval(function () {
+ attempts++;
+ fetch('/api-custom/status/' + videoId)
+ .then(function (sRes) {
+ return sRes.json();
+ })
+ .then(function (sData) {
+ if (sData.isImporting) {
+ showModal(
+ '[1/2] 📥 Video İndiriliyor...',
+ 'Video sunucuya indiriliyor (' + attempts * 2 + ' sn)...'
+ );
+ } else if (sData.isTranscoding) {
+ showModal(
+ '[2/2] ⚙️ Oynatıcı Hazırlanıyor...',
+ 'Video işleniyor ve yüksek kalite akış hazırlanıyor...'
+ );
+ }
+
+ if (sData.isPublished || (sData.hasFiles && attempts >= 8)) {
+ clearInterval(pollInterval);
+ showModal('🎉 Video Hazır!', 'Video kütüphanenize eklendi. Oynatıcı açılıyor...');
+ setTimeout(function () {
+ window.location.href = '/videos/watch/' + (sData.uuid || videoUuid);
+ }, 600);
+ } else if (sData.isFailed) {
+ clearInterval(pollInterval);
+ hideModal();
+ btn.disabled = false;
+ btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
+ alert('Video indirme sırasında bir hata oluştu.');
+ announce('Video indirme sırasında bir hata oluştu.', true);
+ }
+ })
+ .catch(function (e) {
+ console.error('[YakTube] Status poll error:', e);
+ });
+
+ if (attempts > 35) {
+ clearInterval(pollInterval);
+ hideModal();
+ btn.disabled = false;
+ btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
+ alert(
+ 'İndirme arka planda tamamlanıyor. Ana sayfada veya Videolarım sayfasında kısa süre içinde hazır olacaktır.'
+ );
+ announce('İndirme arka planda tamamlanıyor. Ana sayfada kısa süre içinde hazır olacaktır.');
+ }
+ }, 1500);
+ })
+ .catch(function (err) {
+ hideModal();
+ btn.disabled = false;
+ btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
+ alert('İndirme hatası: ' + err.message);
+ announce('İndirme hatası: ' + err.message, true);
+ });
+ });
}
function checkAndInjectSearchResults() {
@@ -3088,6 +3323,9 @@ try {
.then(function (responses) {
var ytData = responses[0] || {};
var localData = responses[1] || {};
+ if (typeof ytData.guestImportLimit === 'number') {
+ cachedGuestImportLimit = ytData.guestImportLimit;
+ }
var rawYtResults = ytData.results || [];
var uniqueYtResults = [];
@@ -4359,6 +4597,9 @@ try {
return res.json();
})
.then(function (data) {
+ if (data && typeof data.guestImportLimit === 'number') {
+ cachedGuestImportLimit = data.guestImportLimit;
+ }
var rawResults = data.results || [];
var normalizedCurrentTitle = rawTitle.toLowerCase().trim();
diff --git a/main.js b/main.js
index 2811505..bf8fa57 100644
--- a/main.js
+++ b/main.js
@@ -182,19 +182,55 @@ async function getAdminToken() {
});
}
-async function register({ getRouter, peertubeHelpers, logger }) {
+async function register({ getRouter, registerSetting, settingsManager, peertubeHelpers, logger }) {
const router = getRouter();
const ytdlpPath = resolveYtDlpPath(logger);
+ let guestImportLimit = 5;
+
+ if (typeof registerSetting === 'function') {
+ registerSetting({
+ name: 'guest-import-limit',
+ label: 'Misafir Video İndirme Sınırı (Kota)',
+ type: 'input',
+ description:
+ 'Giriş yapmamış (misafir) kullanıcıların YouTube üzerinden sunucuya aktarabileceği maksimum video sayısı (Örn: 5). Sınır istemiyorsanız (sınırsız) 0 yazın.',
+ private: false,
+ default: '5'
+ });
+ }
+
+ async function loadPluginSettings() {
+ if (!settingsManager || typeof settingsManager.getSetting !== 'function') return;
+ try {
+ const val = await settingsManager.getSetting('guest-import-limit');
+ if (val !== undefined && val !== null && String(val).trim() !== '') {
+ const parsed = parseInt(String(val).trim(), 10);
+ guestImportLimit = isNaN(parsed) || parsed < 0 ? 5 : parsed;
+ } else {
+ guestImportLimit = 5;
+ }
+ } catch (e) {}
+ }
+
+ await loadPluginSettings();
+ if (settingsManager && typeof settingsManager.onSettingsChange === 'function') {
+ settingsManager.onSettingsChange(loadPluginSettings);
+ }
+
+ router.get('/quota-config', (_req, res) => {
+ return res.json({ ok: true, guestImportLimit });
+ });
+
router.get('/search', async (req, res) => {
const query = (req.query.q || '').trim();
if (!query || query.length < 2) {
- return res.json({ results: [] });
+ return res.json({ results: [], guestImportLimit });
}
const cached = getCached(query);
if (cached) {
- return res.json({ results: cached, cached: true });
+ return res.json({ results: cached, cached: true, guestImportLimit });
}
const args = ['ytsearch6:' + query, '--dump-single-json', '--flat-playlist', '--skip-download', '--no-warnings'];
@@ -233,7 +269,7 @@ async function register({ getRouter, peertubeHelpers, logger }) {
});
setCache(query, results);
- return res.json({ results, cached: false });
+ return res.json({ results, cached: false, guestImportLimit });
} catch (parseErr) {
if (logger) logger.error('JSON parse error in search: ' + parseErr.message);
return res.status(500).json({ error: 'Failed to parse search results' });
@@ -242,11 +278,25 @@ async function register({ getRouter, peertubeHelpers, logger }) {
});
router.post('/import', async (req, res) => {
- const { targetUrl, channelId, category } = req.body;
+ const { targetUrl, channelId, category, isAuthenticated, guestImportCount } = req.body;
if (!targetUrl || (!targetUrl.includes('youtube.com') && !targetUrl.includes('youtu.be'))) {
return res.status(400).json({ error: 'Invalid YouTube URL' });
}
+ const authHeader = req.headers && req.headers.authorization;
+ const isLoggedUser = Boolean(isAuthenticated || (authHeader && authHeader.startsWith('Bearer ')));
+ if (!isLoggedUser && guestImportLimit > 0) {
+ const clientCount = parseInt(String(guestImportCount || '0'), 10) || 0;
+ if (clientCount >= guestImportLimit) {
+ return res.status(429).json({
+ ok: false,
+ quotaExceeded: true,
+ limit: guestImportLimit,
+ error: 'Kota Doldu: Lütfen devam etmek için giriş yapın.'
+ });
+ }
+ }
+
try {
const token = await getAdminToken();
const resolvedChannelId = channelId ? String(channelId) : await getDefaultChannelId(category);
diff --git a/package.json b/package.json
index ed948b7..996cca6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "peertube-plugin-yaktube-ondemand",
- "version": "1.5.6",
+ "version": "1.5.7",
"description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations, Sleep Timer, Touch Gestures, Voice Assistant & A11y Engine for PeerTube",
"main": "main.js",
"library": "./main.js",