115 Commits

Author SHA1 Message Date
ashley
c2aa651b68 Update src/libpoketube/init/pages-static.js 2025-06-03 21:02:56 +02:00
ashley
33cb84b340 Add html/pokepad.ejs 2025-06-03 21:01:16 +02:00
ashley
23cf962230 meta : improve wording in og:description 2025-06-01 14:29:28 +02:00
ashley
2be8727949 Update html/calendar.ejs 2025-05-27 19:20:35 +02:00
ashley
7677d27698 Update html/calendar.ejs 2025-05-27 19:17:03 +02:00
ashley
0fdb869af7 Update html/watch.ejs 2025-05-27 18:30:18 +02:00
ashley
7ee79d8013 Update html/watch.ejs 2025-05-27 18:25:53 +02:00
ashley
79804c063c Update html/watch.ejs 2025-05-27 18:24:12 +02:00
ashley
98d5df7a96 Update html/watch.ejs 2025-05-27 18:19:09 +02:00
ashley
d451401186 Update html/watch.ejs 2025-05-27 18:18:27 +02:00
ashley
046c35119b Update html/watch.ejs 2025-05-27 18:10:35 +02:00
ashley
11faafeaff Update html/watch.ejs 2025-05-27 17:49:53 +02:00
ashley
ec4d53cbed Update html/watch.ejs 2025-05-27 17:46:30 +02:00
ashley
35b83a5d3c Update html/watch.ejs 2025-05-27 17:43:55 +02:00
ashley
64b0529e1b Update html/watch.ejs 2025-05-27 17:26:48 +02:00
ashley
bbdb3be59d Update html/landing.ejs 2025-05-27 17:15:12 +02:00
ashley
10eee1e4b7 Update html/search.ejs 2025-05-27 17:13:26 +02:00
ashley
d564e49d09 oop 2025-05-27 17:09:26 +02:00
ashley
43b8b641a7 Update css/player-base.js 2025-05-27 17:09:02 +02:00
ashley
a3b44a58ee Update html/watch.ejs 2025-05-27 17:05:36 +02:00
ashley
b75dc35a6b Update css/player-base.js 2025-05-27 17:05:10 +02:00
ashley
f7831ad85e Update html/watch.ejs 2025-05-27 17:02:07 +02:00
ashley
07490ec727 Update css/player-base.js 2025-05-27 17:01:41 +02:00
ashley
0654c381c7 Update html/watch.ejs 2025-05-27 16:58:09 +02:00
ashley
26558507db Update css/player-base.js 2025-05-27 16:57:43 +02:00
ashley
daae5abff1 Update html/watch.ejs 2025-05-27 16:27:44 +02:00
ashley
50597f59a7 test code 2025-05-27 16:26:44 +02:00
ashley
10a18905ec Update html/watch.ejs 2025-05-27 02:41:59 +02:00
Ashley ////
f661ee76a9 hai 2025-05-27 02:27:31 +02:00
Ashley ////
024bea2b33 hai 2025-05-27 02:24:52 +02:00
ashley
488d98d1f1 update versions 2025-05-27 01:51:47 +02:00
ashley
9ec1a3a718 fix download pages 2025-05-27 01:47:57 +02:00
ashley
bb28ed0de6 Update html/discover.ejs 2025-05-27 01:42:09 +02:00
ashley
43c2b1111e Update html/discover.ejs 2025-05-27 01:41:09 +02:00
ashley
3f471059f8 Update html/discover.ejs 2025-05-27 01:38:30 +02:00
ashley
46dc456562 Update html/discover.ejs 2025-05-27 01:34:52 +02:00
ashley
0c346781b8 oops 2025-05-27 01:31:03 +02:00
ashley
6824df229b try 2025-05-27 01:28:19 +02:00
ashley
00749f907d Update html/discover.ejs 2025-05-27 01:23:06 +02:00
ashley
e50e7b730a Add bg 2025-05-27 01:19:30 +02:00
ashley
dcf3b20fac Update html/map.ejs 2025-04-30 21:40:49 +00:00
ashley
b91128ff11 Update html/map.ejs 2025-04-30 21:38:53 +00:00
ashley
46abf16976 Update html/map.ejs 2025-04-30 21:34:37 +00:00
ashley
f9d434903d Update html/map.ejs 2025-04-30 21:27:46 +00:00
ashley
dd5aea5304 Update html/map.ejs 2025-04-30 21:24:37 +00:00
ashley
84b92ae3ac Update html/map.ejs 2025-04-30 21:18:07 +00:00
ashley
f05f3e5b3a Update html/translate.ejs 2025-04-30 17:47:21 +00:00
ashley
b4d7b3da11 stuff 2025-04-30 17:09:01 +00:00
ashley
bb1d09ea20 oops 2025-04-30 16:36:31 +00:00
ashley
d708f964eb add stuff 2025-04-30 16:35:26 +00:00
ashley
e4da5d48d4 Update src/libpoketube/init/pages-api.js 2025-04-30 16:08:06 +00:00
ashley
ac979c3bdc Update src/libpoketube/libpoketube-core.js 2025-04-29 19:55:15 +00:00
ashley
f31ff98a0b Update src/libpoketube/libpoketube-core.js 2025-04-29 19:52:34 +00:00
ashley
cc3922ad0e Update src/libpoketube/libpoketube-core.js 2025-04-29 19:50:23 +00:00
ashley
6e1aecaeb4 Update src/libpoketube/libpoketube-core.js 2025-04-29 19:48:46 +00:00
ashley
c9e8b8f85d Update src/libpoketube/libpoketube-core.js 2025-04-29 19:47:04 +00:00
ashley
99f4d03bbb Update src/libpoketube/libpoketube-core.js 2025-04-29 19:39:14 +00:00
ashley
efd3c59df7 Update package.json 2025-04-29 19:32:33 +00:00
ashley
a5a18ba383 use youtubei 2025-04-29 19:31:18 +00:00
ashley
c531678a18 add stuff 2025-04-27 17:13:14 +00:00
ashley
c9bd527527 Update html/gamehub.ejs 2025-04-27 13:59:24 +00:00
ashley
495367eace Update html/layouts/error-video.ejs 2025-04-27 13:13:34 +00:00
ashley
547f68c882 oops 2025-04-27 12:52:28 +00:00
ashley
6268bdebfb new and improved snake 2025-04-27 12:50:08 +00:00
ashley
7527e49c9c stuff 2025-04-27 11:39:29 +00:00
ashley
7125ecc36e Update html/video-error.ejs 2025-04-27 11:12:17 +00:00
ashley
4058e8ef11 Update html/layouts/error-video.ejs 2025-04-27 11:11:49 +00:00
ashley
0527a9e52f remove 2025-04-27 11:10:19 +00:00
ashley
230d371ced fix this :3 2025-04-27 11:05:37 +00:00
ashley
4a8070e65c Update html/gamehub.ejs 2025-04-26 23:43:29 +00:00
ashley
556285a8d1 fix game 2025-04-26 23:38:32 +00:00
ashley
3076fb9393 fix stuff stuff 2025-04-26 23:36:34 +00:00
ashley
777e8522b2 Update html/gamehub.ejs 2025-04-26 23:32:25 +00:00
ashley
efe0f7d758 Update html/gamehub.ejs 2025-04-26 23:28:37 +00:00
ashley
38094732ba Update html/gamehub.ejs 2025-04-26 23:23:23 +00:00
ashley
b8ce39698a oopsie 2025-04-26 23:19:40 +00:00
ashley
4519933c9e Update html/gamehub.ejs 2025-04-26 23:16:33 +00:00
ashley
18635eca02 Update src/libpoketube/init/pages-api.js 2025-04-26 22:55:49 +00:00
ashley
2202193d23 remove unused imports 2025-04-26 22:46:59 +00:00
ashley
b1de0358a7 new stuff 2025-04-26 22:45:40 +00:00
ashley
739d64f684 Update html/watch.ejs 2025-04-26 21:57:04 +00:00
ashley
d539547fd3 Update html/watch.ejs 2025-04-26 21:54:06 +00:00
ashley
c5e67b7f05 Update html/watch.ejs 2025-04-26 21:52:43 +00:00
ashley
d31253551c Update html/partials/card.ejs 2025-04-26 21:41:55 +00:00
ashley
0f40a4a152 add replies 2025-04-26 21:37:42 +00:00
ashley
6e627b48a7 addcool stuff 2025-04-26 21:01:06 +00:00
ashley
704b7bc806 Update html/search.ejs 2025-04-23 20:55:15 +00:00
ashley
f77d5e0260 human friendly reason :3 2025-04-23 20:48:27 +00:00
ashley
5ab4cc30a5 Update html/layouts/error-video.ejs 2025-04-23 20:48:07 +00:00
ashley
afa738f8c2 Update html/search.ejs 2025-04-22 23:55:06 +00:00
ashley
3f19854798 Update html/search.ejs 2025-04-22 23:54:03 +00:00
ashley
6801889f64 Update html/search.ejs 2025-04-22 23:51:16 +00:00
ashley
ce5428eccb a 2025-04-22 23:48:28 +00:00
ashley
3f66e88dae fix stuff stuff 2025-04-22 23:42:32 +00:00
ashley
7ef352f3cb fix stuff 2025-04-22 23:39:39 +00:00
ashley
38871bba36 Update html/search.ejs 2025-04-22 23:29:48 +00:00
ashley
e755ef3f4c oops 2025-04-22 23:28:23 +00:00
ashley
64433797f3 add alot of cool stuff :3 2025-04-22 23:24:35 +00:00
ashley
3f69953875 add error here :3 2025-04-22 18:48:41 +00:00
ashley
b69b2a590e Update css/maps.js 2025-04-22 18:42:37 +00:00
ashley
3558b47301 update
Signed-off-by: ashley <iamashley@duck.com>
2025-04-19 22:53:48 +00:00
ashley
acef288683 sorry for weird formatting mobile 2025-04-19 22:47:39 +00:00
ashley
27becd05a6 test1 2025-04-08 17:24:30 +00:00
ashley
0347a9ab94 update :3 2025-04-08 16:57:17 +00:00
ashley
e775e617ed :3 2025-04-08 16:49:38 +00:00
ashley
59dd95fa39 add error here :3 2025-04-07 14:38:31 +00:00
ashley
fd1f3ea864 Update src/libpoketube/init/pages-static.js 2025-04-06 19:40:09 +00:00
ashley
310ffb643c Update src/libpoketube/init/pages-channel-and-download.js 2025-04-06 19:36:45 +00:00
ashley
4bf687b4a3 Update src/libpoketube/init/pages-channel-and-download.js 2025-04-06 19:33:16 +00:00
ashley
4acf840d80 Update src/libpoketube/init/pages-api.js 2025-04-06 19:27:37 +00:00
ashley
4b33b38e1f p2 2025-04-06 19:22:36 +00:00
ashley
7188bfdf03 Update .gitignore 2025-04-06 19:16:39 +00:00
ashley
440431abbd Update config.json.example 2025-04-06 19:16:18 +00:00
ashley
172a7175ec add useragent to config 2025-04-06 19:02:54 +00:00
ashley
b5e09f60eb add useragent : p1 2025-04-06 19:02:08 +00:00
24 changed files with 4407 additions and 2309 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ yarn.lock
package-lock.json
.env
json.sqlite
config.json

View File

@@ -4,6 +4,7 @@
"dislikes": "https://returnyoutubedislikeapi.com/votes?videoId=",
"invchannel": "https://invid-api.poketube.fun/api/v1",
"p_url":"https://p.poketube.fun",
"useragent":"PokeTube/2.0.0 (GNU/Linux; Android 14; Trisquel 11; poketube-vidious; like FreeTube)",
"media_proxy": "https://image-proxy.poketube.fun",
"videourl":"https://eu-proxy.poketube.fun",
"email_main_url":"https://email-server.poketube.fun",

View File

@@ -1,33 +1 @@
var bbox = "?bbox=-165.76171875000003%2C-3.864254615721396%2C30.410156250000004%2C72.44879155730672&amp;layer=mapnik"
var iframe = document.getElementById('myFrame');
iframe.src=`https://www.openstreetmap.org/export/embed.html${bbox}`
iframe.addEventListener('load', function() {
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
var links = iframeDocument.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function(event) {
var url = event.target.href;
if (url.includes('www.openstreetmap.org')) {
event.preventDefault();
iframe.src = url;
window.history.pushState(null, '', url);
} else {
window.location.href = url;
}
});
}
});
window.onpopstate = function(event) {
iframe.src = window.location.href;
};
iframe.addEventListener('load', function() {
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
var elements = iframeDocument.querySelectorAll('[style*="//dka575ofm4ao0.cloudfront.net"]');
for (var i = 0; i < elements.length; i++) {
var style = elements[i].style.backgroundImage;
var newStyle = style.replace('//dka575ofm4ao0.cloudfront.net', 'https://p.poketube.fun/https://dka575ofm4ao0.cloudfront.net');
elements[i].style.backgroundImage = newStyle;
}
});
(function(){const _0x5a3c=["P2Jib3g9LTE2NS43NjE3MTg3NTAwMDAwMyUyQy0zLjg2NDI1NDYxNTcyMTM5NiUyQzMwLjQxMDE1NjI1MDAwMDAwNCUyQzcyLjQ0ODc5MTU1NzMwNjcyJmxheWVyPW1hcG5paw==","aHR0cHM6Ly93d3cub3BlbnN0cmVldG1hcC5vcmcvZXhwb3J0L2VtYmVkLmh0bWw=","d3d3Lm9wZW5zdHJlZXRtYXAub3Jn"];function _0x99f2(i){return atob(_0x5a3c[i])}async function _0x4f2a(){const bbox=_0x99f2(0);const base=_0x99f2(1);const url=base+bbox;const resp=await fetch(url,{credentials:'include'});const txt=await resp.text();const blob=new Blob([txt],{type:'text/html'});const iframe=document.getElementById('myFrame');iframe.src=URL.createObjectURL(blob);iframe.addEventListener('load',()=>{const doc=iframe.contentDocument||iframe.contentWindow.document;Array.from(doc.querySelectorAll('a')).forEach(a=>a.addEventListener('click',_linkHandler));Array.from(doc.querySelectorAll('*')).forEach(el=>{const bg=el.style.backgroundImage;if(bg.includes('//dka575ofm4ao0.cloudfront.net')){el.style.backgroundImage=bg.replace(/\/\/dka575ofm4ao0\.cloudfront\.net/g,m=>`https://p.poketube.fun/https://dka575ofm4ao0.cloudfront.net`)}})});window.history=new Proxy(window.history,{get(target,prop){if(prop==='pushState'){return(...args)=>{if(args[2]){document.getElementById('myFrame').src=args[2]}return target.pushState.apply(target,args)}}return Reflect.get(target,prop)}});window.addEventListener('popstate',()=>{document.getElementById('myFrame').src=location.href})}function _linkHandler(e){const h=e.target.href;if(h.includes(_0x99f2(2))){e.preventDefault();document.getElementById('myFrame').src=h;window.history.pushState({},'',h)}else{window.location.href=h}}_0x4f2a().catch(console.error)})()};);

View File

@@ -1,111 +1,221 @@
// in the beginning.... god made mrrprpmnaynayaynaynayanyuwuuuwmauwnwanwaumawp :p
var _yt_player = videojs;
document.addEventListener("DOMContentLoaded", () => {
// video.js 8 init - source can be seen in https://poketube.fun/static/vjs.min.js or the vjs.min.js file
const video = videojs('video', {
controls: true,
autoplay: false,
preload: 'auto',
preload: 'auto'
});
// todo : remove this code lol
const qua = new URLSearchParams(window.location.search).get("quality") || "";
localStorage.setItem(`progress-${new URLSearchParams(window.location.search).get('v')}`, 0);
const vidKey = new URLSearchParams(window.location.search).get('v');
localStorage.setItem(`progress-${vidKey}`, 0);
// syncs stuff if used in HD mode
if (qua !== "medium") {
// raw media elements
const videoEl = document.getElementById('video');
const audio = document.getElementById('aud');
const syncVolume = () => {
audio.volume = video.volume();
};
const audioSrc = audio.getAttribute('src');
const vidSrcObj = video.src();
const videoSrc = Array.isArray(vidSrcObj) ? vidSrcObj[0].src : vidSrcObj;
const syncVolumeWithVideo = () => {
video.volume(audio.volume);
};
let audioReady = false, videoReady = false;
let syncInterval = null;
// we check if a video is buffered
const checkAudioBuffer = () => {
const buffered = audio.buffered;
const bufferedEnd = buffered.length > 0 ? buffered.end(buffered.length - 1) : 0;
return audio.currentTime <= bufferedEnd;
};
const isVideoBuffered = () => {
const buffered = video.buffered();
return buffered.length > 0 && buffered.end(buffered.length - 1) >= video.currentTime();
};
// pauses and syncs the video when the seek is finnished :3
const handleSeek = () => {
video.pause();
audio.pause();
if (Math.abs(video.currentTime() - audio.currentTime) > 0.3) {
audio.currentTime = video.currentTime();
// pauses and syncs the video when the seek is finished :3
function clearSyncLoop() {
if (syncInterval) {
clearInterval(syncInterval);
syncInterval = null;
audio.playbackRate = 1;
}
}
if (!checkAudioBuffer()) {
audio.addEventListener('canplay', () => {
if (video.paused && isVideoBuffered()) {
// drift-compensation loop for micro-sync
function startSyncLoop() {
clearSyncLoop();
syncInterval = setInterval(() => {
const vt = video.currentTime();
const at = audio.currentTime;
const delta = vt - at;
// large drift → jump
if (Math.abs(delta) > 0.5) {
audio.currentTime = vt;
audio.playbackRate = 1;
}
// micro drift → adjust rate
else if (Math.abs(delta) > 0.05) {
audio.playbackRate = 1 + delta * 0.1;
} else {
audio.playbackRate = 1;
}
}, 300);
}
// align start when both are ready
function tryStart() {
if (audioReady && videoReady) {
const t = video.currentTime();
if (Math.abs(audio.currentTime - t) > 0.1) {
audio.currentTime = t;
}
video.play();
audio.play();
startSyncLoop();
setupMediaSession();
}
}
// simple one-time retry on error
function attachRetry(elm, src, markReady) {
elm.addEventListener('loadeddata', () => {
markReady();
tryStart();
}, { once: true });
elm.addEventListener('error', () => {
// only retry once, and only if we have a valid src
if (!elm._didRetry && src) {
elm._didRetry = true;
elm.src = src;
elm.load();
} else {
console.error(`${elm.tagName} failed to load.`);
}
}, { once: true });
}
};
const handleBufferingComplete = () => {
if (Math.abs(video.currentTime() - audio.currentTime) > 0.3) {
audio.currentTime = video.currentTime();
// le volume :3
function setupMediaSession() {
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: document.title || 'Video',
artist: '',
album: '',
artwork: []
});
navigator.mediaSession.setActionHandler('play', () => {
video.play(); audio.play();
});
navigator.mediaSession.setActionHandler('pause', () => {
video.pause(); audio.pause();
});
navigator.mediaSession.setActionHandler('seekbackward', ({ seekOffset }) => {
const skip = seekOffset || 10;
video.currentTime(video.currentTime() - skip);
audio.currentTime -= skip;
});
navigator.mediaSession.setActionHandler('seekforward', ({ seekOffset }) => {
const skip = seekOffset || 10;
video.currentTime(video.currentTime() + skip);
audio.currentTime += skip;
});
navigator.mediaSession.setActionHandler('seekto', ({ seekTime, fastSeek }) => {
if (fastSeek && 'fastSeek' in audio) audio.fastSeek(seekTime);
else audio.currentTime = seekTime;
video.currentTime(seekTime);
});
navigator.mediaSession.setActionHandler('stop', () => {
video.pause(); audio.pause();
video.currentTime(0); audio.currentTime = 0;
clearSyncLoop();
});
}
};
}
// ** DESKTOP MEDIA-KEY FALLBACK **
document.addEventListener('keydown', e => {
switch (e.code) {
case 'AudioPlay':
case 'MediaPlayPause':
if (video.paused()) { video.play(); audio.play(); }
else { video.pause(); audio.pause(); }
break;
case 'AudioPause':
video.pause(); audio.pause();
break;
case 'AudioNext':
case 'MediaTrackNext':
const tFwd = video.currentTime() + 10;
video.currentTime(tFwd); audio.currentTime += 10;
break;
case 'AudioPrevious':
case 'MediaTrackPrevious':
const tBwd = video.currentTime() - 10;
video.currentTime(tBwd); audio.currentTime -= 10;
break;
}
});
if (qua !== "medium") {
// attach retry & ready markers to the real elements
attachRetry(audio, audioSrc, () => { audioReady = true; });
attachRetry(videoEl, videoSrc, () => { videoReady = true; });
// Sync when playback starts
video.on('play', () => {
if (!syncInterval) startSyncLoop();
if (Math.abs(video.currentTime() - audio.currentTime) > 0.3) {
audio.currentTime = video.currentTime();
}
if (isVideoBuffered()) {
audio.play();
}
if (audioReady) audio.play();
});
video.on('pause', () => {
audio.pause();
clearSyncLoop();
});
video.on('seeking', handleSeek);
// pause audio when video is buffering :3
video.on('waiting', () => {
audio.pause();
clearSyncLoop();
});
// resume audio when video resumes
video.on('playing', () => {
if (audioReady) audio.play();
if (!syncInterval) startSyncLoop();
});
// pauses and syncs on seek
video.on('seeking', () => {
audio.pause();
clearSyncLoop();
if (Math.abs(video.currentTime() - audio.currentTime) > 0.3) {
audio.currentTime = video.currentTime();
}
});
video.on('seeked', () => {
if (isVideoBuffered()) {
video.play();
}
audio.play();
if (audioReady) audio.play();
if (!syncInterval) startSyncLoop();
});
// le volume :3
video.on('volumechange', syncVolume);
audio.addEventListener('volumechange', syncVolumeWithVideo);
// volume sync
video.on('volumechange', () => {
audio.volume = video.volume();
});
audio.addEventListener('volumechange', () => {
video.volume(audio.volume);
});
// Detects when video or audio finishes buffering
video.on('canplaythrough', handleBufferingComplete);
audio.addEventListener('canplaythrough', handleBufferingComplete);
// media control events
document.addEventListener('play', (e) => {
if (e.target === video) {
audio.play();
video.on('canplaythrough', () => {
if (Math.abs(video.currentTime() - audio.currentTime) > 0.3) {
audio.currentTime = video.currentTime();
}
});
document.addEventListener('pause', (e) => {
if (e.target === video) {
audio.pause();
audio.addEventListener('canplaythrough', () => {
if (Math.abs(video.currentTime() - audio.currentTime) > 0.3) {
audio.currentTime = video.currentTime();
}
});
@@ -114,12 +224,14 @@ document.addEventListener("DOMContentLoaded", () => {
if (!document.fullscreenElement) {
video.pause();
audio.pause();
clearSyncLoop();
}
});
}
});
// hai!! if ur asking why are they here - its for smth in the future!!!!!!
const FORMATS = {

View File

@@ -4,184 +4,176 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="/manifest.json">
<link href="css/yt-ukraine.svg" rel="icon">
<link rel="icon" href="css/yt-ukraine.svg" type="image/svg+xml">
<meta name="theme-color" content="#101010">
<meta name="description" content="Poke! Calendar — zero-JS calendar">
<meta property="og:title" content="Poke! Calendar">
<meta property="og:description" content="Navigate months without JavaScript needed">
<meta property="og:image" content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884">
<meta property="og:type" content="website">
<meta property="og:url" content="https://yourdomain.com/calendar">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@PokeCalendar">
<meta name="twitter:creator" content="@YourHandle">
<meta name="twitter:title" content="Poke! Calendar">
<meta name="twitter:image" content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884">
<title>Poke! Calendar</title>
<meta content="PokeCalendar" property="og:title">
<meta content="Worlds first no js web calendar :3" property="twitter:description">
<meta content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884" property="og:image" />
<meta content=summary_large_image name=twitter:card>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
<meta name="referrer" content="no-referrer">
<style>
:root {
--bg: #101010;
--panel: #1a1a1a;
--border: #2a2a2a;
--accent: #bb86fc;
--accent-light: #ce9eff;
--text: #e0e0e0;
--today: #3700b3;
--weekend: #121212;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background-color: #121212;
color: #ffffff;
font-family: Arial, sans-serif;
margin: 0;
background: var(--bg) url('/css/background.jpg') center/cover fixed no-repeat;
color: var(--text);
font-family: 'Inter', sans-serif;
min-height: 100vh;
line-height: 1.5;
}
body::before {
content: '';
position: fixed; inset: 0;
background: inherit;
filter: blur(16px) brightness(0.4);
z-index: -1;
}
.navbar {
background-color: #333333;
padding: 10px;
display: flex;
align-items: center; /* Center items vertically */
justify-content: space-between; /* Space items evenly */
position: sticky; top: 0;
display: flex; align-items: center; justify-content: space-between;
padding: 1rem 2rem;
background: rgba(26,26,26,0.8);
border-bottom: 1px solid var(--border);
}
.navbar h1 {
margin: 0;
color: #bb86fc;
}
.navbar .years {
color: #bb86fc; /* Year text color */
display: flex; /* Use flexbox for alignment */
gap: 20px; /* Space between year elements */
flex-wrap: wrap; /* Allow wrapping on smaller screens */
justify-content: center; /* Center items on smaller screens */
}
.navbar img { width: 8em; }
.years { display: flex; gap: 1.5rem; flex-wrap: wrap; }
.years h2 { font-size: 0.95rem; color: var(--accent); }
.container {
text-align: center;
padding: 20px;
background-color: #1e1e1e;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
width: 90%;
max-width: 800px;
margin: auto;
width: 90%; max-width: 900px;
margin: 2rem auto;
padding: 2rem;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
}
h2, h3 {
color: #bb86fc;
margin: 10px 0; /* Margin between h2 elements */
.header-row {
display: flex; flex-wrap: wrap;
align-items: center; justify-content: space-between;
margin-bottom: 1.5rem;
}
.month-title {
font-size: 1.5em; /* Adjust the size as needed */
margin: 20px 0; /* Spacing above and below */
color: #bb86fc; /* Month title color */
.month-title { font-size: 2rem; color: var(--accent); }
.month-picker {
padding: 0.4rem 0.8rem;
font-size: 1rem;
color: var(--text);
background: var(--panel);
border: 1px solid var(--border);
border-radius: 8px;
}
.month-button {
margin-left: 0.5rem;
padding: 0.5rem 1rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
}
.calendar-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
width: 100%; border-collapse: collapse; table-layout: fixed;
}
.calendar-table th, .calendar-table td {
padding: 15px;
padding: 1rem; border: 1px solid var(--border);
text-align: center;
border: 1px solid #333333;
font-size: 0.9em; /* Smaller font size for better fit */
}
.calendar-table th {
background-color: #333333;
background: var(--panel);
color: var(--accent-light);
font-weight: 500;
}
.calendar-table td {
background-color: #2c2c2c;
color: #ffffff;
background: var(--panel);
color: var(--text);
}
.calendar-table td:nth-child(1),
.calendar-table td:nth-child(7) {
background: var(--weekend);
}
.calendar-table td.today {
background: var(--today) !important;
color: #fff;
border-color: var(--accent);
}
.nav-links {
margin-top: 20px;
display: flex; justify-content: center; gap: 1rem;
margin-top: 2rem;
}
.button {
padding: 0.75rem 1.5rem;
background: var(--accent);
color: #fff;
text-decoration: none;
color: #ffffff;
background-color: #bb86fc;
padding: 10px 20px;
border-radius: 5px;
margin: 0 10px;
transition: background-color 0.3s;
border-radius: 8px;
border: 1px solid var(--accent-light);
cursor: pointer;
font-weight: 500;
}
.button:hover {
background-color: #9c62f3;
}
/* Responsive styles */
@media (max-width: 768px) {
.navbar {
flex-direction: column; /* Stack navbar items vertically on small screens */
align-items: center; /* Center items horizontally */
.container { padding: 1rem; }
.month-title { font-size: 1.5rem; }
.calendar-table th, .calendar-table td { padding: 0.75rem; font-size: 0.85rem; }
.nav-links { flex-direction: column; }
.button { width: 100%; }
}
.container {
width: 100%; /* Full width on small screens */
height: 100vh; /* Full height of the viewport */
border-radius: 0; /* Remove border-radius for full-screen effect */
box-shadow: none; /* Remove shadow for a flatter design */
padding: 10px; /* Adjust padding for mobile */
}
.calendar-table th, .calendar-table td {
padding: 10px; /* Reduced padding for smaller screens */
font-size: 0.8em; /* Smaller font size */
}
.month-title {
font-size: 1.2em; /* Smaller month title */
}
.button {
padding: 8px 15px; /* Smaller button size */
margin: 5px 0; /* Vertical spacing */
display: block; /* Stack buttons vertically */
width: 100%; /* Full width */
}
}
</style>
</head>
<body>
<div class="navbar">
<a class="class" href="/143" style="font-family: Inter, sans-serif; color: #fff">
<img style="transform: scale(1.3); padding-left: 0.9em; width: 8.5em; display: block; margin-left: auto; margin-right: auto;" src="/css/logo-poke.svg?v=5">
</a>
<a href="/143"><img src="/css/logo-poke.svg?v=5" alt="Poke Calendar Logo"></a>
<div class="years">
<h2>Gregorian Year: <%= year %></h2>
<h2>Islamic Year: <%= islamicYear %></h2>
<h2>Persian Year: <%= persianYear %></h2>
</div>
</div>
<div class="container" style="margin-top: 1em;">
<h2 class="month-title"><%= queryDate.toLocaleString('default', { month: 'long' }) %> <%= year %></h2> <!-- Month and Year Display -->
<div class="container">
<div class="header-row">
<h2 class="month-title"><%= queryDate.toLocaleString('default', { month: 'long' }) %> <%= year %></h2>
<form action="/calendar" method="get" style="display:flex; align-items:center;">
<input type="month" name="date" value="<%= currentDate.toISOString().slice(0,7) %>" class="month-picker">
<button type="submit" class="month-button">Go</button>
</form>
</div>
<table class="calendar-table">
<thead>
<tr>
<th>Sunday</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th>
</tr>
</thead>
<tbody>
<% for (let i = 0; i < 6; i++) { %>
<tr>
<% for (let j = 0; j < 7; j++) { %>
<td>
<% const day = days[i * 7 + j]; %>
<%= day ? day.getDate() : '' %>
</td>
<% } %>
</tr>
<% } %>
<% days.forEach((day, idx) => { %>
<% if (idx % 7 === 0) { %><tr><% } %>
<% const today = new Date(); %>
<% const isToday = day &&
day.getDate() === today.getDate() &&
day.getMonth() === today.getMonth() &&
day.getFullYear() === today.getFullYear(); %>
<td class="<%= isToday ? 'today' : '' %>"><%= day ? day.getDate() : '' %></td>
<% if (idx % 7 === 6) { %></tr><% } %>
<% }); %>
</tbody>
</table>
<div class="nav-links">
<a href="/calendar?date=<%= new Date(currentDate.getFullYear(), month - 1, 1).toISOString() %>" class="button">Previous Month</a>
<a href="/calendar?date=<%= new Date(currentDate.getFullYear(), month + 1, 1).toISOString() %>" class="button">Next Month</a>
<a href="/calendar?date=<%= new Date(year, month - 1, 1).toISOString() %>" class="button">Prev</a>
<a href="/calendar?date=<%= new Date(year, month + 1, 1).toISOString() %>" class="button">Next </a>
</div>
</div>
</body>

View File

@@ -1138,8 +1138,8 @@ Privacy
<% if (tab == "search") { %>
<a href="/app"><-- go back to trendsz </a><br>
<br> <span>
<a href="/app"><-- go to home </a>
<span style="display:none">
Search videos on poke! poke is a youtube front end so all of the videos on youtube should workm!!
</span>
<div class=search>
@@ -1147,10 +1147,10 @@ Privacy
<form action="/app">
<input class="search-bar" autocomplete="on" id="fname" name="mobilesearch" placeholder="Search some videos lol "style="color:#fff;font-family:Inter,sans-serif;border-radius: 8px;">
<input class="search-bar" autocomplete="on" id="fname" name="mobilesearch" placeholder="Search! "style="background: linear-gradient(90deg, hsla(235, 21%, 21%, 1) 0%, hsla(194, 41%, 22%, 1) 50%, hsla(174, 48%, 20%, 1) 100%);color:#fff;font-family:Inter,sans-serif;border-radius: 9999px;">
<button class="btn btn-success" style="border-radius:1em" type=submit>
<button class="btn btn-success" style="border-radius:1em;display:none;" type=submit>
<i class="fa-light fa-search" style="margin: auto;"></i></button></form>
<img src="https://t.poketube.fun/t/rep.gif" style="border:0;width: 0;visibility: hidden;">

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
This Source Code Form is subject to the terms of the GNU General Public License:
Copyright (C) 2021-2024 Poke (https://codeberg.org/Ashley/poke)
Copyright (C) 2021-2025 Poke (https://codeberg.org/ashley/poke)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,14 +33,14 @@
<link href=/css/yt-ukraine.svg?v=7 rel=icon>
<link rel="manifest" href="/manifest.json">
<meta name="darkreader-lock"> <!-- tells dark reader that the site has a dark theme and to turn itself off -->
<meta content="▶▶ Poke - The privacy app of your dreams!" property=og:title>
<meta content="▶▶ Poke - The only (good) front-end in the world!" property=og:title>
<% if(embedtype === "woke") { %>
<meta content="Poke is a 𝔀𝓸𝓴𝓮 software YouTube front-end, search engine, translator, 𝔀𝓸𝓴𝓮 app, and even 𝔀𝓸𝓴𝓮!! Watch 𝔀𝓸𝓴𝓮 videos, search the internet, and do all of that and more 𝔀𝓸𝓴𝓮 in this all-in-one 𝔀𝓸𝓴𝓮 app!!!"
<meta content="Poke is a 𝔀𝓸𝓴𝓮 software YouTube front-end, translator, 𝔀𝓸𝓴𝓮 app, and even 𝔀𝓸𝓴𝓮!! Watch 𝔀𝓸𝓴𝓮 videos, search the internet, and do all of that and more 𝔀𝓸𝓴𝓮 in this all-in-one 𝔀𝓸𝓴𝓮 app!!!"
property="twitter:description">
<meta content="https://cdn.glitch.global/302c6ee0-629f-453b-9024-bad1f8d7be36/9fhFiXJ.png?v=1717357642758"
property="og:image">
<% } else { %>
<meta content="Poke is a free software YouTube front-end, search engine, translator, map app, and more!! Watch silly videos, search the internet, and do all of that and more anonymously in this all-in-one privacy app!!!"
<meta content="Poke is a free software YouTube front-end, translator, map app, and more!! Watch silly videos, search the internet, and do all of that and more anonymously in this all-in-one privacy app!!!"
property="twitter:description">
<meta content="https://cdn.glitch.global/302c6ee0-629f-453b-9024-bad1f8d7be36/poke.png?v=1716216428745"
property="og:image">
@@ -117,8 +117,8 @@
<%- include('./partials/header.ejs') %>
<video playsinline muted paused><source src="/bg-480.webm" type="video/webm"/></video>
<div class="landing">
<h1 style="text-align: center;">GUESS WHO'S BACK ...</h1>
<p style="max-width: 800px;text-align: center;margin: auto;margin-bottom: 3em;">...back again.. to save you from big tech and embrace the freedom of privacy!!1! :3 Poke is a free software YouTube front-end, search engine, translator, map app, and more!!1! Watch videos, search the web, and explore without a trace in this all-in-one privacy app!!1! :3</p>
<h1 style="text-align: center;">WELCOME TO POKE!</h1>
<p style="max-width: 800px;text-align: center;margin: auto;margin-bottom: 3em;">Poke is a free software AD-FREE ! YouTube front-end, translator, map app, and more!!1! Watch videos and explore without a trace in this all-in-one privacy app!!1! :3</p>
<div style="text-align: center; padding: 10px; border-radius: 8px;margin-left: -1em;">
<details>
<summary style="cursor: pointer; color: white; font-size: 18px; text-decoration: underline;">
@@ -152,11 +152,13 @@
<img src="/static/Poke-Mobile.jpg" />
</div>
<div>
<h1 style="margin-left: auto;margin-right: auto;text-align: center;margin-bottom: -1em;margin-top: 1em;">TOP 3 REASONS WHY POKE IZ COOL!!</h1>
</div>
<%
const features = [
{ title: "No Tracking and Ads", description: "Poke Has no Trackers or ads - we dont and we wont see the vids ur watching :3", icon: "<svg style='background: #ea6d6d;' width='24px' height='24px' viewBox='0 0 24 24' stroke-width='1.5' fill='none' xmlns='http://www.w3.org/2000/svg' color='#ffffff'><path d='M19.5 16L17.0248 12.6038' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M12 17.5V14' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M4.5 16L6.96895 12.6124' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M3 8C6.6 16 17.4 16 21 8' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>" },
{ title: "Very Fast", description: "Poke is really ligthweight (both on server and client :3) so you can still use it on poor connections :3", icon: "<svg style='background: #6d8cea;' width='24px' height='24px' stroke-width='1.5' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' color='#ffffff'><path d='M15 7C16.1046 7 17 6.10457 17 5C17 3.89543 16.1046 3 15 3C13.8954 3 13 3.89543 13 5C13 6.10457 13.8954 7 15 7Z' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M12.6133 8.26691L9.30505 12.4021L13.4403 16.5374L11.3727 21.0861' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M6.4104 9.5075L9.79728 6.19931L12.6132 8.26692L15.508 11.5752H19.2297' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M8.89152 15.7103L7.65095 16.5374H4.34277' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>" },
{ title: "Speedy", description: "Poke is really ligthweight (both on server and client :3) so you can still use it on poor connections :3", icon: "<svg style='background: #6d8cea;' width='24px' height='24px' stroke-width='1.5' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' color='#ffffff'><path d='M15 7C16.1046 7 17 6.10457 17 5C17 3.89543 16.1046 3 15 3C13.8954 3 13 3.89543 13 5C13 6.10457 13.8954 7 15 7Z' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M12.6133 8.26691L9.30505 12.4021L13.4403 16.5374L11.3727 21.0861' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M6.4104 9.5075L9.79728 6.19931L12.6132 8.26692L15.508 11.5752H19.2297' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M8.89152 15.7103L7.65095 16.5374H4.34277' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>" },
{ title: "Downloader", description: "You wouldnt download a car - welp i would :D u can download videos from poke for 0$!", icon: "<svg style='background: #519355;' width='24px' height='24px' stroke-width='1.5' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' color='#ffffff'><path d='M12 8V16M12 16L15.5 12.5M12 16L8.5 12.5' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>" },
{ title: "DRM Free", description: "Poke comes without digital restrictions management - poke is free software :3", icon: "<svg style='background: #b7a358;' width='24px' height='24px' stroke-width='1.5' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' color='#ffffff'><path d='M4.62323 5.24841C2.99408 7.02743 2 9.39765 2 12C2 17.5229 6.47715 22 12 22C14.5361 22 16.8517 21.0559 18.6146 19.5' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M21.3021 15.6775C21.7525 14.5392 22 13.2985 22 12C22 6.47715 17.5228 2 12 2C10.7687 2 9.58934 2.22255 8.5 2.62961' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M9 15C9.64448 15.8593 10.8428 16.3494 12 16.391C13.1141 16.431 14.1901 16.0554 14.6973 15.1933' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M12 16.391V18.5' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M9.5 9.5C9.5 10.6811 10.3525 11.1647 11.3862 11.5' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M15 8.5C14.315 7.81501 13.1087 7.33855 12 7.30872V5.5' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M3 3L21 21' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>" },
{ title: "Games Included", description: "U can play funnie games on poke!", icon: "<svg style='background: #886dea;' width='24px' height='24px' viewBox='0 0 24 24' stroke-width='1.5' fill='none' xmlns='http://www.w3.org/2000/svg' color='#ffffff'><path d='M17.5 17.5C20 21 23.9486 18.4151 23 15C21.5753 9.87113 20.8001 7.01556 20.3969 5.50793C20.1597 4.62136 19.3562 4 18.4384 4L5.56155 4C4.64382 4 3.844 4.62481 3.62085 5.515C2.7815 8.86349 2.0326 11.8016 1.14415 15C0.195501 18.4151 4.14415 21 6.64415 17.5' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M16 4V6C16 7.10457 15.1046 8 14 8H10C8.89543 8 8 7.10457 8 6L8 4' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M8 16C9.10457 16 10 15.1046 10 14C10 12.8954 9.10457 12 8 12C6.89543 12 6 12.8954 6 14C6 15.1046 6.89543 16 8 16Z' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/><path d='M16 16C17.1046 16 18 15.1046 18 14C18 12.8954 17.1046 12 16 12C14.8954 12 14 12.8954 14 14C14 15.1046 14.8954 16 16 16Z' stroke='#ffffff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>" }
@@ -188,9 +190,6 @@ const randomFeatures = features.sort(() => 0.5 - Math.random()).slice(0, 3);
secondary_text='Discord',
secondary_link='https://discord.poketube.fun',
primary_icon='<svg version="1.1" viewBox="0 0 27.9 32" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="translate(-.095 .005)" fill="#040404"><path d="m27.1 31.2v-30.5h-2.19v-0.732h3.04v32h-3.04v-0.732z"/><path d="m8.23 10.4v1.54h0.044c0.385-0.564 0.893-1.03 1.49-1.37 0.58-0.323 1.25-0.485 1.99-0.485 0.72 0 1.38 0.14 1.97 0.42 0.595 0.279 1.05 0.771 1.36 1.48 0.338-0.5 0.796-0.941 1.38-1.32 0.58-0.383 1.27-0.574 2.06-0.574 0.602 0 1.16 0.074 1.67 0.22 0.514 0.148 0.954 0.383 .32 0.707 0.366 0.323 0.653 0.746 0.859 1.27 0.205 0.522 0.308 1.15 0.308 1.89v7.63h-3.13v-6.46c0-0.383-0.015-0.743-0.044-1.08-0.0209-0.307-0.103-0.607-0.242-0.882-0.133-0.251-0.336-0.458-0.584-0.596-0.257-0.146-0.606-0.22-1.05-0.22-0.44 0-0.796 0.085-1.07 0.253-0.272 0.17-0.485 0.39-0.639 0.662-0.159 0.287-0.264 0.602-0.308 0.927-0.052 0.347-0.078 0.697-0.078 1.05v6.35h-3.13v-6.4c0-0.338-7e-3 -0.673-0.021-1-0.0114-0.314-0.0749-0.623-0.188-0.916-0.108-0.277-0.3-0.512-0.55-0.673-0.258-0.168-0.636-0.253-1.14-0.253-0.198 0.0083-0.394 0.042-0.584 0.1-0.258 0.0745-0.498 0.202-0.705 0.374-0.228 0.184-0.422 0.449-0.584 0.794-0.161 0.346-0.242 0.798-0.242 1.36v6.62h-3.13v-11.4z"/><path d="m0.936 0.732v30.5h2.19v0.732h-3.04v-32h3.03v0.732z"/></g></svg>',
primary_text='Matrix',
primary_link='https://matrix.to/#/%23poke:vern.cc'
)%>
<%- include('./partials/card',

View File

@@ -5,9 +5,6 @@
<meta content="#111111" name="theme-color" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, viewport-fit=cover" />
<meta name="darkreader-lock" />
<% if (description == "This helps protect our community. Learn more (TRYING AGAIN....)") { %>
<meta http-equiv="refresh" content="5">
<% } %>
<link href="/css/yt-ukraine.svg?v=3" rel="icon" />
<link rel="manifest" href="/manifest.json" />
<style>
@@ -81,26 +78,66 @@
.error-footer a:hover {
color: #d69cc8;
}
.countdown {
margin-top: 16px;
color: #ccc;
font-size: 14px;
}
</style>
<% const RESTART_MSG = "Poke is currently restarting - please wait 1-2 minutes.."; %>
<% if (description === RESTART_MSG) { %>
<script>
// Client-side reload logic
let reloadCount = parseInt(localStorage.getItem('reloadCount') || '0', 10);
reloadCount++;
localStorage.setItem('reloadCount', reloadCount);
let seconds = reloadCount > 5 ? 30 : 10;
function updateCountdown() {
const el = document.getElementById('countdown');
if (el) {
if (reloadCount > 5) {
document.querySelector('.error p').textContent = "This is taking so long...";
}
el.textContent = "Trying again in " + seconds + " seconds...";
}
if (seconds <= 0) {
location.reload();
} else {
seconds--;
setTimeout(updateCountdown, 1000);
}
}
window.addEventListener('DOMContentLoaded', updateCountdown);
</script>
<% } else { %>
<script>
// Clear reload count on non-restart errors
localStorage.removeItem('reloadCount');
</script>
<% } %>
</head>
<body>
<% if (description == "This helps protect our community. Learn more (TRYING AGAIN....)") { %>
<% if (description === RESTART_MSG) { %>
<p id="abstract">502</p>
<% } else if (description !== "This helps protect our community. Learn more (TRYING AGAIN....)") { %>
<% } else { %>
<p id="abstract">404</p>
<% } %>
<div class="error">
<h2><%= error %></h2>
<p><%= description %></p>
<% if (description === RESTART_MSG) { %>
<div class="countdown" id="countdown"></div>
<% } %>
</div>
<div class="error-footer">
<a href="https://codeberg.org/ashley/poke/issues/new/choose">Create issue</a>
<a href="https://discord.poketube.fun">Report on our Discord</a>
<% if (description == "This helps protect our community. Learn more (TRYING AGAIN....)") { %>
<% if (description === RESTART_MSG) { %>
<a href="https://github.com/iv-org/invidious/issues">See Invidious issues</a>
<a href="">Refresh Page</a>
<% } %>

View File

@@ -22,7 +22,7 @@
* @licstart The following is the entire license notice for the JavaScript
* code in this page.
*
* Copyright (C) 2021-2023 POKETUBE (https://github.com/iamashley0/poketube)
* Copyright (C) 2021-2025 POKETUBE (https://github.com/iamashley0/poketube)
*
* The JavaScript code in this page is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
@@ -44,6 +44,178 @@
//--><!]]>
</script>
<script src="/static/maps.js"></script><script src="/static/data-mobile.js"></script>
<script>(function(){
const _0x5a3c=[
"P2Jib3g9LTE2NS43NjE3MTg3NTAwMDAwMyUyQy0zLjg2NDI1NDYxNTcyMTM5NiUyQzMwLjQxMDE1NjI1MDAwMDAwNCUyQzcyLjQ0ODc5MTU1NzMwNjcyJmxheWVyPW1hcG5paw==",
"aHR0cHM6Ly93d3cub3BlbnN0cmVldG1hcC5vcmcvZXhwb3J0L2VtYmVkLmh0bWw=",
"d3d3Lm9wZW5zdHJlZXRtYXAub3Jn"
];
function _0x99f2(i){ return atob(_0x5a3c[i]); }
function updateMap(lat, lon) {
const delta = 0.25;
const bbox = `?bbox=${lon-delta},${lat-delta},${lon+delta},${lat+delta}&layer=mapnik`;
const newURL = _0x99f2(1) + bbox;
const iframe = document.querySelector('iframe');
if (iframe) {
iframe.src = newURL;
window.history.pushState({}, '', newURL);
}
const marker = document.getElementById('map-marker');
if (marker) marker.remove();
const newMarker = document.createElement('div');
newMarker.id = 'map-marker';
newMarker.style = 'position:absolute;width:20px;height:20px;background:red;border-radius:50%;transform:translate(-50%,-50%);z-index:9998;pointer-events:none;left:50%;top:50%';
document.body.appendChild(newMarker);
}
function copyCoordinates() {
const marker = document.getElementById('map-marker');
if (!marker) return alert('No coordinates to copy.');
navigator.clipboard.writeText(window.location.href).then(() => {
alert('Current map link copied to clipboard!');
});
}
function locateAndUpdate() {
if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser.');
return;
}
navigator.geolocation.getCurrentPosition(
pos => {
updateMap(pos.coords.latitude, pos.coords.longitude);
},
err => {
alert('Unable to retrieve location: ' + err.message);
},
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 0 }
);
}
function _0x4f2a(){
const bbox = _0x99f2(0);
const base = _0x99f2(1);
const url = base + bbox;
const iframe = document.querySelector('iframe');
if (!iframe) return setTimeout(_0x4f2a, 100);
iframe.src = url;
iframe.addEventListener('load',()=>{
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
Array.from(doc.querySelectorAll('a')).forEach(a=>a.addEventListener('click',_linkHandler));
Array.from(doc.querySelectorAll('*')).forEach(el=>{
const bg = el.style.backgroundImage;
if(bg.includes('//dka575ofm4ao0.cloudfront.net')){
el.style.backgroundImage = bg.replace(/\/\/dka575ofm4ao0\.cloudfront\.net/g,
m=>`https://p.poketube.fun/https://dka575ofm4ao0.cloudfront.net`);
}
});
} catch(e) {
console.warn('Cross-origin access denied, skipping DOM manipulation.');
}
});
window.history = new Proxy(window.history,{
get(target, prop){
if(prop === 'pushState') return (...args)=>{
const iframe = document.querySelector('iframe');
if(iframe && args[2]) iframe.src = args[2];
return target.pushState.apply(target, args);
};
return Reflect.get(target, prop);
}
});
window.addEventListener('popstate',()=>{
const iframe = document.querySelector('iframe');
if (iframe) iframe.src = location.href;
});
}
function _linkHandler(e){
const h = e.target.href;
const iframe = document.querySelector('iframe');
if(!iframe) return;
if(h.includes(_0x99f2(2))){
e.preventDefault();
iframe.src = h;
window.history.pushState({}, '', h);
} else {
window.location.href = h;
}
}
const form = document.createElement('form');
form.style = 'position:absolute;top:10px;right:10px;z-index:9999;background:rgba(0,0,0,0.5);backdrop-filter:blur(12px);padding:10px 12px;border-radius:12px;box-shadow:0 4px 10px rgba(0,0,0,0.4);font-family:sans-serif;min-width:220px;';
form.innerHTML = `
<input id="searchBox" type="text" placeholder="Search..." style="padding:6px 10px;width:180px;font-size:14px;border:1px solid #444;border-radius:6px;background:#222;color:#fff">
<ul id="suggestions" style="list-style:none;margin:6px 0 0;padding:0;max-height:180px;overflow:auto;background:#111;border:1px solid #333;border-radius:6px;display:none;position:relative;z-index:10000;color:#fff;"></ul>
<div style="margin-top:10px;display:flex;gap:6px;flex-wrap:wrap">
<button id="locate-btn" type="button" style="flex:1;padding:4px 6px;font-size:12px;background:#333;color:#fff;border:none;border-radius:6px">📍 Locate</button>
<button type="button" style="flex:1;padding:4px 6px;font-size:12px;background:#333;color:#fff;border:none;border-radius:6px" onclick="copyCoordinates()">📋 Copy</button>
<button type="button" style="flex:1;padding:4px 6px;font-size:12px;background:#333;color:#fff;border:none;border-radius:6px" onclick="location.reload()">🔁 Reset</button>
</div>
`;
document.body.appendChild(form);
document.getElementById('locate-btn').addEventListener('click', locateAndUpdate);
const input = form.querySelector('#searchBox');
const suggestions = form.querySelector('#suggestions');
input.addEventListener('input', () => {
const query = input.value.trim();
if (!query) return suggestions.style.display = 'none';
fetch(`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=5`)
.then(res => res.json())
.then(data => {
suggestions.innerHTML = '';
data.forEach(place => {
const li = document.createElement('li');
li.textContent = place.display_name;
li.style = 'padding:6px 10px;cursor:pointer;border-bottom:1px solid #222;font-size:13px;background:#111';
li.addEventListener('click', () => {
input.value = place.display_name;
suggestions.style.display = 'none';
updateMap(parseFloat(place.lat), parseFloat(place.lon));
});
suggestions.appendChild(li);
});
suggestions.style.display = 'block';
});
});
form.addEventListener('submit', e => {
e.preventDefault();
const q = input.value.trim();
if(!q) return;
fetch(`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(q)}&format=json&limit=1`)
.then(res => res.json())
.then(data => {
if(data[0]){
updateMap(parseFloat(data[0].lat), parseFloat(data[0].lon));
}
});
});
const fab = document.createElement('button');
fab.textContent = '+';
fab.style = 'position:fixed;bottom:20px;right:20px;width:48px;height:48px;font-size:24px;background:#111;color:#fff;border:none;border-radius:50%;box-shadow:0 2px 10px rgba(0,0,0,0.4);cursor:pointer;z-index:9999';
fab.title = 'More Tools';
fab.onclick = () => alert('More features coming soon!');
document.body.appendChild(fab);
const branding = document.createElement('div');
branding.textContent = 'PokeMaps';
branding.style = 'position: absolute; bottom: 10px; left: 10px; padding: 6px 10px; font-size: 31px; font-weight: 500; background: rgba(0, 0, 0, 0.6); color: white; border-radius: 6px; font-family: sans-serif; backdrop-filter: blur(6px); z-index: 9999; pointer-events: none;display: block;';
document.body.appendChild(branding);
_0x4f2a();
})();
</script><script src="/static/data-mobile.js"></script>
</body>
</html>

View File

@@ -11,7 +11,6 @@
<% if (has_secondary_action=="true") { %>
<a class="card-secondary" href="<%= secondary_link %>"><%- secondary_icon %> <%= secondary_text %></a>
<% } %>
<a class="card-primary" href="<%= primary_link %>"><%- primary_icon %> <%= primary_text %></a>
</div>
<% }%>
</div>

804
html/pokepad.ejs Normal file
View File

@@ -0,0 +1,804 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pokepad</title>
<meta property="og:title" content="PokePad">
<meta property="og:description" content="E2EE notepad">
<meta property="og:image" content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884">
<meta property="og:type" content="website">
<style>
/* Root colors */
:root {
--bg: #0f0f17;
--container-bg: rgba(30, 30, 47, 0.6);
--surface: rgba(20, 20, 35, 0.4);
--text: #e0e0e0;
--accent: #8a2be2;
--border: #444458;
--btn-bg: rgba(60, 60, 80, 0.7);
--btn-hover: rgba(70, 70, 100, 0.8);
--error: #f66;
--success: #8f8;
--tab-hover: rgba(100, 100, 120, 0.8);
--tab-active-bg: rgba(10, 10, 15, 0.9);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
background: var(--bg);
color: var(--text);
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
}
#container {
width: 100%;
max-width: 900px;
background: var(--container-bg);
border: 1px solid var(--border);
border-radius: 12px;
backdrop-filter: blur(12px);
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.5);
position: relative;
}
header {
width: 100%;
background: var(--surface);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
}
header .title {
font-size: 1.5rem;
color: var(--accent);
display: flex;
align-items: center;
gap: 0.5rem;
}
header .title svg {
width: 24px;
height: 24px;
fill: var(--accent);
}
header .e2ee {
font-size: 0.9rem;
padding: 0.25rem 0.5rem;
background: var(--btn-bg);
border-radius: 4px;
border: 1px solid var(--border);
cursor: pointer;
}
#e2eePopup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--container-bg);
border: 1px solid var(--border);
border-radius: 8px;
backdrop-filter: blur(12px);
padding: 1rem;
max-width: 300px;
display: none;
z-index: 10;
}
#e2eePopup p {
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
#e2eePopup button {
background: var(--accent);
border: none;
border-radius: 4px;
color: #fff;
padding: 0.5rem 1rem;
cursor: pointer;
font-size: 0.9rem;
}
#tabBar {
display: flex;
background: var(--surface);
border-bottom: 1px solid var(--border);
overflow-x: auto;
white-space: nowrap;
}
.tab {
display: flex;
align-items: center;
padding: 0.5rem 0.75rem;
cursor: pointer;
user-select: none;
font-size: 0.95rem;
border-right: 1px solid var(--border);
color: var(--text);
background: var(--surface);
transition: background 0.2s, color 0.2s;
position: relative;
flex-shrink: 0;
}
.tab:hover {
background: var(--tab-hover);
}
.tab.active {
background: var(--tab-active-bg);
color: var(--accent);
border-bottom: 2px solid var(--accent);
}
.tab .title-text {
margin-right: 0.5rem;
}
.tab .icon-btn {
background: transparent;
border: none;
color: var(--text);
cursor: pointer;
padding: 0;
margin-left: 0.25rem;
display: flex;
align-items: center;
}
.tab .icon-btn:hover {
color: var(--accent);
}
.tab[data-dragging="true"] {
opacity: 0.5;
}
#editor {
flex: 1;
background: rgba(20, 20, 35, 0.3);
backdrop-filter: blur(8px);
padding: 1rem;
overflow-y: auto;
min-height: 300px;
color: var(--text);
}
#editor:empty:before {
content: attr(data-placeholder);
color: #888;
}
#editor:focus {
outline: 2px solid var(--accent);
}
#toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.tool-btn {
background: var(--btn-bg);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
padding: 0.5rem;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.2s;
display: flex;
align-items: center;
}
.tool-btn svg {
width: 18px;
height: 18px;
fill: var(--text);
margin-right: 0.3rem;
}
.tool-btn:hover {
background: var(--btn-hover);
}
#controls {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: var(--surface);
border-top: 1px solid var(--border);
}
#controls input[type="password"] {
flex: 1;
padding: 0.5rem;
font-size: 1rem;
border: 1px solid var(--border);
border-radius: 4px;
background: rgba(20, 20, 35, 0.6);
color: var(--text);
}
#controls .action-btn {
background: var(--btn-bg);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
padding: 0.5rem 1rem;
cursor: pointer;
font-size: 0.95rem;
transition: background 0.2s;
}
#controls .action-btn:hover {
background: var(--btn-hover);
}
#message {
padding: 0.5rem 1rem;
font-size: 0.9rem;
min-height: 1.2em;
}
#message.error {
color: var(--error);
}
#message.success {
color: var(--success);
}
#fileInput {
display: none;
}
</style>
</head>
<body>
<div id="container">
<!-- Header with icon and E2EE label -->
<header>
<div class="title">
<!-- Lock SVG -->
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C9.238 2 7 4.238 7 7v3H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-1V7c0-2.762-2.238-5-5-5zm-3 5c0-1.654 1.346-3 3-3s3 1.346 3 3v3H9V7zm-1 5h8v7H8v-7z"/>
</svg>
Pokepad
</div>
<div class="e2ee" id="e2eeLabel">End-to-End Encrypted</div>
</header>
<!-- E2EE Info Popup -->
<div id="e2eePopup">
<p><strong>End-to-End Encryption (E2EE)</strong></p>
<p>All notes are encrypted locally before being saved. Only you (and anyone you share your password with) can decrypt and read your notes. No unencrypted data ever leaves your browser.</p>
<button id="e2eeCloseBtn">Close</button>
</div>
<!-- Tab bar -->
<div id="tabBar"></div>
<!-- Formatting toolbar -->
<div id="toolbar">
<!-- Bold -->
<button class="tool-btn" data-cmd="bold" title="Bold (Ctrl+B)">
<svg viewBox="0 0 24 24"><path d="M15.6 10.79c.75-.54 1.4-1.31 1.85-2.18.46-.87.7-1.86.7-2.93 0-1.07-.24-2.06-.7-2.93-.46-.87-1.1-1.64-1.85-2.18-.81-.59-1.77-.89-2.77-.89h-5v16h5c1 0 1.96-.3 2.77-.89.75-.54 1.4-1.31 1.85-2.18.46-.87.7-1.86.7-2.93 0-1.07-.24-2.06-.7-2.93zM11 4h1.5c.74 0 1.44.26 2 .72.56.46 1.01 1.11 1.28 1.8.27.69.42 1.45.42 2.22 0 .77-.15 1.53-.42 2.22-.27.69-.72 1.34-1.28 1.8-.56.46-1.26.72-2 .72h-1.5V4zm1.5 12H11c-.74 0-1.44-.26-2-.72-.56-.46-1.01-1.11-1.28-1.8-.27-.69-.42-1.45-.42-2.22 0-.77.15-1.53.42-2.22.27-.69.72-1.34 1.28-1.8.56-.46 1.26-.72 2-.72h1.5v9z"/></svg>
<span>Bold</span>
</button>
<!-- Italic -->
<button class="tool-btn" data-cmd="italic" title="Italic (Ctrl+I)">
<svg viewBox="0 0 24 24"><path d="M10 4v3h2.21l-3.42 10H6v3h8v-3h-2.21l3.42-10H18V4z"/></svg>
<span>Italic</span>
</button>
<!-- Underline -->
<button class="tool-btn" data-cmd="underline" title="Underline (Ctrl+U)">
<svg viewBox="0 0 24 24"><path d="M12 17c3.31 0 6-2.69 6-6V4h-2v7c0 2.21-1.79 4-4 4s-4-1.79-4-4V4H6v7c0 3.31 2.69 6 6 6zm-5 2v2h10v-2H7z"/></svg>
<span>Underline</span>
</button>
<!-- Strikethrough -->
<button class="tool-btn" data-cmd="strikeThrough" title="Strikethrough">
<svg viewBox="0 0 24 24"><path d="M10 19v-2H5.41L9 13.41 7.59 12 2 17.59V19h8zm4 0h8v-2h-5.59L15 13.41 13.59 12 8 17.59V19h6zM21 11h-8V9h8v2z"/></svg>
<span>Strike</span>
</button>
<!-- Unordered List -->
<button class="tool-btn" data-cmd="insertUnorderedList" title="Bullet List">
<svg viewBox="0 0 24 24"><path d="M4 10.5c.83 0 1.5-.67 1.5-1.5S4.83 7.5 4 7.5 2.5 8.17 2.5 9 3.17 10.5 4 10.5zm0 5c.83 0 1.5-.67 1.5-1.5S4.83 12.5 4 12.5 2.5 13.17 2.5 14 3.17 15.5 4 15.5zm0 5c.83 0 1.5-.67 1.5-1.5S4.83 17.5 4 17.5 2.5 18.17 2.5 19 3.17 20.5 4 20.5zM7 9h14v2H7V9zm0 5h14v2H7v-2zm0 5h14v2H7v-2z"/></svg>
<span>Bullets</span>
</button>
<!-- Ordered List -->
<button class="tool-btn" data-cmd="insertOrderedList" title="Numbered List">
<svg viewBox="0 0 24 24"><path d="M4 10h2v1H4v2h2v1H4v2h4v-1H6v-2h2v-1H4v-2zm0-4h4v1H6v2h2v1H4v2h4v1H4v2h6v-1H6v-2h2v-1H4v-2zm0 10h12v-1H4v-2h2v-1H4v-2h4v-1H4V6h6V5H4v2h4v1H4v2h4v1H4v2z"/></svg>
<span>Numbers</span>
</button>
<!-- Align Left -->
<button class="tool-btn" data-cmd="justifyLeft" title="Align Left">
<svg viewBox="0 0 24 24"><path d="M3 5h18v2H3V5zm0 4h12v2H3V9zm0 4h18v2H3v-2zm0 4h12v2H3v-2z"/></svg>
<span>Left</span>
</button>
<!-- Align Center -->
<button class="tool-btn" data-cmd="justifyCenter" title="Align Center">
<svg viewBox="0 0 24 24"><path d="M3 5h18v2H3V5zm3 4h12v2H6V9zm3 4h18v2H9v-2zm3 4h12v2H12v-2z"/></svg>
<span>Center</span>
</button>
<!-- Align Right -->
<button class="tool-btn" data-cmd="justifyRight" title="Align Right">
<svg viewBox="0 0 24 24"><path d="M3 5h18v2H3V5zm6 4h12v2H9V9zm6 4h18v2H15v-2zm6 4h12v2h-12v-2z"/></svg>
<span>Right</span>
</button>
<!-- Text Color -->
<button class="tool-btn" id="colorPickerBtn" title="Text Color">
<svg viewBox="0 0 24 24"><path d="M15.55 14.52L9.48 4.5H7.03l6.07 10.02c.18.3.28.64.28 1 0 1.1-.9 2-2 2s-2-.9-2-2H7c0 1.66 1.34 3 3 3s3-1.34 3-3c0-.61-.22-1.17-.58-1.6zM12 2C8.13 2 5 5.13 5 9c0 1.66.58 3.18 1.55 4.38L12 22l5.45-8.62C18.42 12.18 19 10.66 19 9c0-3.87-3.13-7-7-7z"/></svg>
<span>Color</span>
</button>
<input type="color" id="colorPicker" style="display:none" />
<!-- Insert Link -->
<button class="tool-btn" data-cmd="createLink" title="Insert Link">
<svg viewBox="0 0 24 24"><path d="M3.9 12c0-1.17.44-2.27 1.24-3.06l3.34-3.34c1.65-1.65 4.33-1.65 5.98 0 1.65 1.65 1.65 4.33 0 5.98l-1.06 1.06-1.41-1.41 1.06-1.06c.88-.88.88-2.31 0-3.19-.88-.88-2.31-.88-3.19 0l-3.34 3.34c-.88.88-.88 2.31 0 3.19.88.88 2.31.88 3.19 0l1.06-1.06 1.41 1.41-1.06 1.06c-1.65 1.65-4.33 1.65-5.98 0C4.34 14.27 3.9 13.17 3.9 12zm16.2 0c0 1.17-.44 2.27-1.24 3.06l-3.34 3.34c-1.65 1.65-4.33 1.65-5.98 0-1.65-1.65-1.65-4.33 0-5.98l1.06-1.06 1.41 1.41-1.06 1.06c-.88.88-.88 2.31 0 3.19.88.88 2.31.88 3.19 0l3.34-3.34c.88-.88.88-2.31 0-3.19-.88-.88-2.31-.88-3.19 0l-1.06 1.06-1.41-1.41 1.06-1.06c1.65-1.65 4.33-1.65 5.98 0 1.65 1.65 1.65 4.33 0 5.98z"/></svg>
<span>Link</span>
</button>
<!-- Unlink -->
<button class="tool-btn" data-cmd="unlink" title="Remove Link">
<svg viewBox="0 0 24 24"><path d="M12.71 11.29l-1.42 1.42L10 11.42l-1.29 1.29-1.42-1.42L8.58 10 7.29 8.71l1.42-1.42L10 8.58l1.29-1.29 1.42 1.42L11.42 10l1.29 1.29zM17.65 6.35l-1.41 1.41 1.41 1.41L19.06 7.76l-1.41-1.41zm-11.3 11.3l-1.41 1.41 1.41 1.41 1.41-1.41-1.41-1.41z"/></svg>
<span>Unlink</span>
</button>
</div>
<div id="editor" contenteditable="true" spellcheck="false" data-placeholder="Type your notes here..."></div>
<div id="controls">
<input type="password" id="password" placeholder="Password" />
<button class="action-btn" id="saveBtn">Download&nbsp;Encrypted</button>
<button class="action-btn" id="loadBtn">Decrypt&nbsp;&amp;&nbsp;Load</button>
<button class="action-btn" id="uploadBtn">Upload&nbsp;Encrypted&nbsp;File</button>
<input type="file" id="fileInput" accept=".json" />
</div>
<div id="message"></div>
</div>
<script>
let notes = [];
let currentTabId = null;
let tabCounter = 0;
let dragSrcId = null;
const STORAGE_KEY = 'pokepadEncrypted';
const PLAIN_KEY = 'pokepadPlain';
const tabBar = document.getElementById('tabBar');
const editor = document.getElementById('editor');
const passwordInput = document.getElementById('password');
const saveBtn = document.getElementById('saveBtn');
const loadBtn = document.getElementById('loadBtn');
const uploadBtn = document.getElementById('uploadBtn');
const fileInput = document.getElementById('fileInput');
const messageDiv = document.getElementById('message');
const toolbarButtons = document.querySelectorAll('#toolbar .tool-btn');
const e2eeLabel = document.getElementById('e2eeLabel');
const e2eePopup = document.getElementById('e2eePopup');
const e2eeCloseBtn = document.getElementById('e2eeCloseBtn');
const colorPicker = document.getElementById('colorPicker');
const colorPickerBtn = document.getElementById('colorPickerBtn');
// On load: attempt to load plain data so tabs persist
window.addEventListener('DOMContentLoaded', () => {
const storedPlain = localStorage.getItem(PLAIN_KEY);
if (storedPlain) {
try {
const parsedPlain = JSON.parse(storedPlain);
if (parsedPlain.notes && Array.isArray(parsedPlain.notes)) {
notes = parsedPlain.notes;
tabCounter = notes.length ? Math.max(...notes.map(n => n.id)) + 1 : 0;
currentTabId = parsedPlain.currentTabId != null
? parsedPlain.currentTabId
: notes[0]?.id;
renderTabs();
if (currentTabId != null) {
const curr = notes.find(n => n.id === currentTabId);
editor.innerHTML = curr ? curr.content : '';
}
return;
}
} catch {}
}
// If no plain data, create default tab
createNewTab();
setInterval(savePlain, 10000);
});
function createNewTab(name = null, content = '') {
const id = tabCounter++;
const defaultName = name || `Note ${id + 1}`;
notes.push({ id, name: defaultName, content });
switchToTab(id);
renderTabs();
savePlain();
}
function renderTabs() {
tabBar.innerHTML = '';
notes.forEach((note) => {
const tabEl = document.createElement('div');
tabEl.className = 'tab' + (note.id === currentTabId ? ' active' : '');
tabEl.setAttribute('draggable', 'true');
tabEl.dataset.id = note.id;
const titleSpan = document.createElement('span');
titleSpan.className = 'title-text';
titleSpan.textContent = note.name;
tabEl.appendChild(titleSpan);
const renameBtn = document.createElement('span');
renameBtn.className = 'icon-btn';
renameBtn.textContent = '✎';
renameBtn.title = 'Rename tab';
renameBtn.addEventListener('click', (e) => {
e.stopPropagation();
promptRename(note.id, titleSpan);
});
tabEl.appendChild(renameBtn);
const closeBtn = document.createElement('span');
closeBtn.className = 'icon-btn';
closeBtn.textContent = '×';
closeBtn.title = 'Close tab';
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
closeTab(note.id);
});
tabEl.appendChild(closeBtn);
tabEl.addEventListener('click', () => switchToTab(note.id));
tabEl.addEventListener('dragstart', tabDragStart);
tabEl.addEventListener('dragover', tabDragOver);
tabEl.addEventListener('drop', tabDrop);
tabEl.addEventListener('dragend', tabDragEnd);
tabBar.appendChild(tabEl);
});
const newTabEl = document.createElement('div');
newTabEl.className = 'tab';
newTabEl.textContent = '+';
newTabEl.title = 'Add new tab';
newTabEl.addEventListener('click', () => createNewTab());
tabBar.appendChild(newTabEl);
}
function switchToTab(id) {
if (currentTabId !== null) {
const currentNote = notes.find(n => n.id === currentTabId);
if (currentNote) currentNote.content = editor.innerHTML;
}
currentTabId = id;
const nextNote = notes.find(n => n.id === id);
editor.innerHTML = nextNote ? nextNote.content : '';
renderTabs();
clearMessage();
editor.focus();
savePlain();
}
function promptRename(id, titleSpan) {
const note = notes.find(n => n.id === id);
if (!note) return;
const input = document.createElement('input');
input.type = 'text';
input.value = note.name;
input.style.fontSize = '0.95rem';
input.style.background = 'rgba(20,20,35,0.6)';
input.style.color = 'var(--text)';
input.style.border = '1px solid var(--border)';
input.style.borderRadius = '4px';
input.style.padding = '2px 4px';
titleSpan.replaceWith(input);
input.focus();
input.select();
input.addEventListener('blur', () => {
const newName = input.value.trim();
if (newName) note.name = newName;
renderTabs();
savePlain();
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') input.blur();
});
}
function closeTab(id) {
const idx = notes.findIndex(n => n.id === id);
if (idx === -1) return;
const wasActive = (id === currentTabId);
notes.splice(idx, 1);
if (notes.length === 0) {
tabCounter = 0;
notes = [];
createNewTab();
return;
}
if (wasActive) {
const newIdx = idx > 0 ? idx - 1 : 0;
switchToTab(notes[newIdx].id);
} else {
renderTabs();
savePlain();
}
}
function tabDragStart(e) {
dragSrcId = Number(e.currentTarget.dataset.id);
e.currentTarget.dataset.dragging = 'true';
}
function tabDragOver(e) {
e.preventDefault();
const targetId = Number(e.currentTarget.dataset.id);
if (dragSrcId === targetId) return;
const srcIndex = notes.findIndex(n => n.id === dragSrcId);
const tgtIndex = notes.findIndex(n => n.id === targetId);
notes.splice(tgtIndex, 0, notes.splice(srcIndex, 1)[0]);
renderTabs();
}
function tabDrop(e) {
e.stopPropagation();
savePlain();
}
function tabDragEnd(e) {
delete e.currentTarget.dataset.dragging;
}
// Save editor changes
editor.addEventListener('input', () => {
if (currentTabId !== null) {
const note = notes.find(n => n.id === currentTabId);
if (note) note.content = editor.innerHTML;
}
savePlain();
});
function savePlain() {
if (currentTabId !== null) {
const currentNote = notes.find(n => n.id === currentTabId);
if (currentNote) currentNote.content = editor.innerHTML;
}
const payload = { notes, currentTabId };
localStorage.setItem(PLAIN_KEY, JSON.stringify(payload));
}
async function getKeyMaterial(password) {
const encoder = new TextEncoder();
return crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
}
async function deriveKey(password, salt) {
const keyMaterial = await getKeyMaterial(password);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: salt, iterations: 150000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let b of bytes) {
binary += String.fromCharCode(b);
}
return btoa(binary);
}
function base64ToArrayBuffer(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
async function encryptData(plainText, password) {
const encoder = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(password, salt);
const cipherBuffer = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
encoder.encode(plainText)
);
return {
salt: arrayBufferToBase64(salt.buffer),
iv: arrayBufferToBase64(iv.buffer),
data: arrayBufferToBase64(cipherBuffer)
};
}
async function decryptData(encryptedObj, password) {
const salt = base64ToArrayBuffer(encryptedObj.salt);
const iv = base64ToArrayBuffer(encryptedObj.iv);
const cipherBuffer = base64ToArrayBuffer(encryptedObj.data);
const key = await deriveKey(password, salt);
try {
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
key,
cipherBuffer
);
const decoder = new TextDecoder();
return decoder.decode(decryptedBuffer);
} catch {
throw new Error('Decryption failed. Wrong password or corrupted data.');
}
}
saveBtn.addEventListener('click', async () => {
clearMessage();
const pw = passwordInput.value;
if (!pw) {
showMessage('Please enter a password before downloading.', 'error');
return;
}
if (currentTabId !== null) {
const currentNote = notes.find(n => n.id === currentTabId);
if (currentNote) currentNote.content = editor.innerHTML;
}
savePlain();
const payload = { notes, currentTabId };
const jsonString = JSON.stringify(payload);
try {
const encrypted = await encryptData(jsonString, pw);
localStorage.setItem(STORAGE_KEY, JSON.stringify(encrypted));
const downloadObj = JSON.stringify(encrypted);
const blob = new Blob([downloadObj], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const timestamp = new Date().toISOString().slice(0,19).replace(/[:T]/g, '-');
a.download = `pokepad_${timestamp}.json`;
a.href = url;
a.click();
URL.revokeObjectURL(url);
showMessage('Encrypted file ready to download.', 'success');
} catch {
showMessage('Error during encryption. Try again.', 'error');
}
});
uploadBtn.addEventListener('click', () => { clearMessage(); fileInput.click(); });
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (e) => {
clearMessage();
const pw = passwordInput.value;
if (!pw) {
showMessage('Enter password to decrypt uploaded file.', 'error');
return;
}
try {
const encryptedObj = JSON.parse(e.target.result);
const decryptedText = await decryptData(encryptedObj, pw);
const parsed = JSON.parse(decryptedText);
if (!parsed.notes || !Array.isArray(parsed.notes)) {
showMessage('Invalid file format.', 'error');
return;
}
notes = parsed.notes.map(n => ({ id: n.id, name: n.name, content: n.content }));
tabCounter = notes.length ? Math.max(...notes.map(n => n.id)) + 1 : 0;
currentTabId = parsed.currentTabId != null ? parsed.currentTabId : (notes[0]?.id);
renderTabs();
if (currentTabId != null) {
const curr = notes.find(n => n.id === currentTabId);
editor.innerHTML = curr ? curr.content : '';
} else if (notes[0]) {
currentTabId = notes[0].id;
editor.innerHTML = notes[0].content;
}
savePlain();
showMessage('Decryption successful! Notes loaded.', 'success');
} catch {
showMessage('Failed to decrypt or parse file.', 'error');
}
};
reader.readAsText(file);
fileInput.value = '';
});
loadBtn.addEventListener('click', async () => {
clearMessage();
const pw = passwordInput.value;
if (!pw) {
showMessage('Enter password to decrypt stored data.', 'error');
return;
}
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) {
showMessage('No locally stored data found.', 'error');
return;
}
try {
const encryptedObj = JSON.parse(stored);
const decryptedText = await decryptData(encryptedObj, pw);
const parsed = JSON.parse(decryptedText);
if (!parsed.notes || !Array.isArray(parsed.notes)) {
showMessage('Invalid local data format.', 'error');
return;
}
notes = parsed.notes.map(n => ({ id: n.id, name: n.name, content: n.content }));
tabCounter = notes.length ? Math.max(...notes.map(n => n.id)) + 1 : 0;
currentTabId = parsed.currentTabId != null ? parsed.currentTabId : (notes[0]?.id);
renderTabs();
if (currentTabId != null) {
const curr = notes.find(n => n.id === currentTabId);
editor.innerHTML = curr ? curr.content : '';
} else if (notes[0]) {
currentTabId = notes[0].id;
editor.innerHTML = notes[0].content;
}
showMessage('Decryption successful! Notes loaded from localStorage.', 'success');
savePlain();
} catch {
showMessage('Decryption failed or data corrupted.', 'error');
}
});
window.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
e.preventDefault();
showMessage('PokePad autosaves :3 ', 'success');
}
});
toolbarButtons.forEach(btn => {
const cmd = btn.getAttribute('data-cmd');
btn.addEventListener('click', () => {
if (cmd === 'createLink') {
const url = prompt('Enter the URL:', 'https://');
if (url) document.execCommand(cmd, false, url);
} else {
document.execCommand(cmd, false, null);
}
editor.focus();
});
});
colorPickerBtn.addEventListener('click', () => {
colorPicker.click();
});
colorPicker.addEventListener('input', () => {
document.execCommand('foreColor', false, colorPicker.value);
editor.focus();
});
e2eeLabel.addEventListener('click', () => {
e2eePopup.style.display = 'block';
});
e2eeCloseBtn.addEventListener('click', () => {
e2eePopup.style.display = 'none';
});
function showMessage(text, type) {
messageDiv.textContent = text;
messageDiv.className = type;
setTimeout(() => {
if (messageDiv.textContent === text) clearMessage();
}, 4000);
}
function clearMessage() {
messageDiv.textContent = '';
messageDiv.className = '';
}
</script>
</body>
</html>

View File

@@ -416,7 +416,7 @@ video[counter].classList.add("shake");
<div class="middle">
<div class="search">
<form action=/search><input class=search-bar autocomplete="on" value="<%=q%>" id=fname name=query style="color:#fff;font-family:Inter,sans-serif;border-radius: 8px;">
<form action=/search><input class=search-bar autocomplete="on" value="<%=q%>" id=fname name=query style="color:#fff;font-family:Inter,sans-serif;border-radius: 8px;background: linear-gradient(90deg, hsla(235, 21%, 21%, 1) 0%, hsla(194, 41%, 22%, 1) 50%, hsla(174, 48%, 20%, 1) 100%);" >
<button class="btn btn-success" type=submit><i class="fa-light fa-search"></i></button>
</form>
@@ -424,7 +424,10 @@ video[counter].classList.add("shake");
</div> </div>
<div class=right>
<div class=right style="background: #333;
border-radius: 2em;
height: 49px;
margin-top: 5px;">
<button title="Play/Pause Ambient music" class="a" id="audioButton" onclick="toggleAudio()">
<i id="audioIcon" class="fas fa-pause"></i>
@@ -452,7 +455,7 @@ video[counter].classList.add("shake");
<div id="filters-box"><form action="/search" method="get">
<input type="hidden" name="query" value="<%- q %>">
<input type="hidden" name="continuation" value="<%- continuation %>">
<div id="filters-flex" style="display: flex;gap: 18px;background: #111;border-radius: 1em;padding: 6px;width: fit-content;margin-left: auto;margin-right: auto;"> <div class="filter-column"><fieldset>
<div id="filters-flex" style="display: flex;gap: 18px;background: #111;border-radius: 1em;margin-top: 6px;padding: 6px;width: fit-content;margin-left: auto;margin-right: auto;"> <div class="filter-column"><fieldset>
<legend><div class="filter-name underlined">Upload date</div></legend>
<div class="filter-options">
<% const selectedDate = date || "none"; %>
@@ -494,7 +497,8 @@ video[counter].classList.add("shake");
<% }) %>
</div> </fieldset></div>
<br> <div id="filters-apply"> <button type="submit" style="color:#fff;background:#333;padding:3px;border-radius:11px;margin-top: 6em;">Apply!</button></div>
<br> <div id="filters-apply">
<button type="submit" style="color:#fff;background:#333;padding:6px;border-radius:9px;margin-top: 6em;border: 1px solid gray;">Apply!</button></div>
</div>
</form></div> </details></div>
@@ -511,21 +515,20 @@ Web </a>
</div>
</div>
<%
const query = q.toLowerCase().trim();
let answer = '';
function isMathExpression(query) {
return /^[0-9\s\+\-\*\/\.\x]+$/.test(query);
}
function evaluateMathExpression(expression) {
// twenyone
if (expression.replace(/\s+/g, '') === '9+10') {
return '21';
}
try {
return eval(expression);
} catch (error) {
return 'Invalid Expression';
}
if (/^[0-9\s+\-*/.x]+$/.test(query)) {
const expr = query.replace(/\s+/g, '');
answer = expr === '9+10' ? '21' : (() => {
try { return eval(expr); } catch { return 'Invalid Expression'; }
})();
return answer;
}
function getCurrentDate() {
@@ -538,7 +541,7 @@ function getCurrentYear() {
}
function getTimeInTimezone(location) {
const timezones = {
const tzMap = {
"california": "America/Los_Angeles",
"new york": "America/New_York",
"chicago": "America/Chicago",
@@ -568,9 +571,7 @@ function getTimeInTimezone(location) {
"jakarta": "Asia/Jakarta",
"delhi": "Asia/Kolkata",
"mumbai": "Asia/Kolkata",
"kolkata": "Asia/Kolkata",
"karachi": "Asia/Karachi",
"lahore": "Asia/Karachi",
"dubai": "Asia/Dubai",
"abu dhabi": "Asia/Dubai",
"riyadh": "Asia/Riyadh",
@@ -586,10 +587,92 @@ function getTimeInTimezone(location) {
"vienna": "Europe/Vienna",
"stockholm": "Europe/Stockholm",
"oslo": "Europe/Oslo",
"helsinki": "Europe/Helsinki"
"helsinki": "Europe/Helsinki",
"prague": "Europe/Prague",
"budapest": "Europe/Budapest",
"warsaw": "Europe/Warsaw",
"bucharest": "Europe/Bucharest",
"sofia": "Europe/Sofia",
"zagreb": "Europe/Zagreb",
"belgrade": "Europe/Belgrade",
"sarajevo": "Europe/Sarajevo",
"podgorica": "Europe/Podgorica",
"ljubljana": "Europe/Ljubljana",
"tirana": "Europe/Tirane",
"valletta": "Europe/Malta",
"andorra la vella": "Europe/Andorra",
"monaco": "Europe/Monaco",
"luxembourg": "Europe/Luxembourg",
"bratislava": "Europe/Bratislava",
"vilnius": "Europe/Vilnius",
"riga": "Europe/Riga",
"tallinn": "Europe/Tallinn",
"istanbul": "Europe/Istanbul",
"jerusalem": "Asia/Jerusalem",
"amman": "Asia/Amman",
"beirut": "Asia/Beirut",
"damascus": "Asia/Damascus",
"baghdad": "Asia/Baghdad",
"tehran": "Asia/Tehran",
"islamabad": "Asia/Karachi",
"kathmandu": "Asia/Kathmandu",
"thimphu": "Asia/Thimphu",
"dhaka": "Asia/Dhaka",
"yangon": "Asia/Yangon",
"hanoi": "Asia/Ho_Chi_Minh",
"ho chi minh city": "Asia/Ho_Chi_Minh",
"manila": "Asia/Manila",
"singapore": "Asia/Singapore",
"kuala lumpur": "Asia/Kuala_Lumpur",
"colombo": "Asia/Colombo",
"bagotville": "America/Montreal",
"toronto": "America/Toronto",
"vancouver": "America/Vancouver",
"mexico city": "America/Mexico_City",
"guadalajara": "America/Mexico_City",
"monterrey": "America/Monterrey",
"sao paulo": "America/Sao_Paulo",
"buenos aires": "America/Argentina/Buenos_Aires",
"santiago": "America/Santiago",
"rio de janeiro": "America/Sao_Paulo",
"caracas": "America/Caracas",
"bogota": "America/Bogota",
"lima": "America/Lima",
"quito": "America/Quito",
"georgetown": "America/Guyana",
"paramaribo": "America/Paramaribo",
"cayenne": "America/Cayenne",
"kingston": "America/Jamaica",
"port_of_spain": "America/Port_of_Spain",
"st johns": "America/St_Johns",
"midway": "Pacific/Midway",
"apia": "Pacific/Apia",
"nuku alofa": "Pacific/Tongatapu",
"tarawa": "Pacific/Tarawa",
"funafuti": "Pacific/Funafuti",
"suva": "Pacific/Fiji",
"chatham": "Pacific/Chatham",
"pitcairn": "Pacific/Pitcairn",
"galapagos": "Pacific/Galapagos",
"easter island": "Pacific/Easter",
"honiara": "Pacific/Guadalcanal",
"port vila": "Pacific/Efate",
"palikir": "Pacific/Pohnpei",
"palau": "Pacific/Palau",
"mcmurdo": "Antarctica/McMurdo",
"rothera": "Antarctica/Rothera",
"troll": "Antarctica/Troll",
"davis": "Antarctica/Davis",
"casey": "Antarctica/Casey",
"mawson": "Antarctica/Mawson",
"vostok": "Antarctica/Vostok",
"syowa": "Antarctica/Syowa",
"gmt": "Etc/GMT",
"utc": "UTC"
};
const timezone = timezones[location.toLowerCase()];
const timezone = tzMap[location.toLowerCase()];
if (!timezone) {
return null;
}
@@ -599,9 +682,186 @@ function getTimeInTimezone(location) {
return now.toLocaleTimeString('en-US', options);
}
const query = q.toLowerCase().trim();
let answer = '';
function randomCompliment() {
const compliments = [
"Youre an awesome friend.",
"You light up the room.",
"You have a great sense of humor.",
"You make a difference.",
"You're like a ray of sunshine."
];
return compliments[Math.floor(Math.random() * compliments.length)];
}
function randomEncouragement() {
const encourages = [
"Keep going—youve got this!",
"Dont give up now!",
"Every step counts.",
"Believe in yourself.",
"You can do anything."
];
return encourages[Math.floor(Math.random() * encourages.length)];
}
function base64Encode(str) {
return btoa(str);
}
function base64Decode(str) {
try {
return atob(str);
} catch {
return 'Invalid Base64';
}
}
function hexToDec(str) {
return parseInt(str.replace(/^0x/, ''), 16).toString();
}
function decToHex(num) {
return Number(num).toString(16);
}
function textToCharCodes(text) {
return text.split('').map(c => c.charCodeAt(0)).join(' ');
}
function charCodesToText(codes) {
return String.fromCharCode(...codes.split(/\s+/).map(n => +n));
}
function countWords(text) {
return text.trim().split(/\s+/).length;
}
function titleCase(text) {
return text
.split(' ')
.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
.join(' ');
}
function slugify(text) {
return text
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
function isPrime(n) {
n = Number(n);
if (n < 2) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) return false;
}
return true;
}
function gcd(a, b) {
a = Math.abs(a); b = Math.abs(b);
while (b) [a, b] = [b, a % b];
return a;
}
function lcm(a, b) {
return Math.abs(a * b) / gcd(a, b);
}
function fibonacci(n) {
n = Number(n);
if (n < 2) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) [a, b] = [b, a + b];
return b;
}
function generatePassword(length = 8) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()';
let pw = '';
for (let i = 0; i < length; i++) {
pw += chars.charAt(Math.floor(Math.random() * chars.length));
}
return pw;
}
function daysUntil(dateStr) {
const now = new Date();
const then = new Date(dateStr);
const diff = then - now;
return diff > 0
? Math.ceil(diff / (1000 * 60 * 60 * 24)) + ' days'
: 'Date passed';
}
function generateUUID() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(
/[018]/g,
c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
function generateRandomString(length = 8) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let s = '';
for (let i = 0; i < length; i++) {
s += chars.charAt(Math.floor(Math.random() * chars.length));
}
return s;
}
const emojiMap = {
"grinning face":"😀","grin":"😁","smiley":"😃","smile":"😄","sweat smile":"😅",
"joy":"😂","rofl":"🤣","relaxed":"☺️","blush":"😊","innocent":"😇",
"slightly_smiling_face":"🙂","upside_down":"🙃","wink":"😉","relieved":"😌",
"heart eyes":"😍","kissing_heart":"😘","kissing":"😗","kissing_smiling_eyes":"😙",
"kissing_closed_eyes":"😚","yum":"😋","stuck out tongue":"😛","stuck out tongue wink":"😜",
"stuck out tongue closed eyes":"😝","money_mouth":"🤑","hug":"🤗","nerd":"🤓",
"sunglasses":"😎","star_struck":"🤩","thinking":"🤔","zipper_mouth":"🤐",
"raised_eyebrow":"🤨","neutral":"😐","expressionless":"😑","no_mouth":"😶",
"roll_eyes":"🙄","smirk":"😏","persevere":"😣","disappointed_relieved":"😥",
"cold_sweat":"😰","pensive":"😔","confused":"😕","worried":"😟","slightly_frowning":"🙁",
"frowning":"☹️","anguished":"😧","open_mouth":"😮","grimacing":"😬","hushed":"😯",
"astonished":"😲","flushed":"😳","pleading":"🥺","frowning2":"😦","anguished2":"😧",
"cry":"😢","disappointed":"😞","scream":"😱","fearful":"😨","tired_face":"😫",
"weary":"😩","triumph":"😤","angry":"😠","rage":"😡","poop":"💩","thumbsup":"👍",
"thumbsdown":"👎","clap":"👏","raised_hands":"🙌","pray":"🙏","muscle":"💪","eyes":"👀",
"ear":"👂","nose":"👃","tongue":"👅","lips":"👄","kiss":"💋","crown":"👑","womans hat":"👒",
"eyeglasses":"👓","necktie":"👔","shirt":"👕","jeans":"👖","dress":"👗","kimono":"👘",
"bikini":"👙","sandal":"👡","boot":"👢","mans shoe":"👞","high heel":"👠","sock":"🧦",
"gloves":"🧤","scarf":"🧣","tophat":"🎩","billed cap":"🧢","flag us":"🇺🇸","flag gb":"🇬🇧",
"flag ca":"🇨🇦","flag de":"🇩🇪","flag fr":"🇫🇷","flag es":"🇪🇸","flag it":"🇮🇹","flag jp":"🇯🇵",
"flag cn":"🇨🇳","flag in":"🇮🇳","flag br":"🇧🇷","flag ru":"🇷🇺","flag za":"🇿🇦","flag au":"🇦🇺",
"flag nz":"🇳🇿","flag ng":"🇳🇬","flag eg":"🇪🇬","flag ar":"🇦🇷","flag mx":"🇲🇽","flag kr":"🇰🇷",
"flag tr":"🇹🇷",
// animals
"dog face":"🐶","cat face":"🐱","mouse face":"🐭","hamster":"🐹","rabbit":"🐰",
"fox face":"🦊","bear face":"🐻","koala":"🐨","tiger face":"🐯","lion face":"🦁",
"cow face":"🐮","pig face":"🐷","frog face":"🐸","octopus":"🐙","monkey face":"🐵",
"chicken":"🐔","penguin":"🐧","bird":"🐦","baby chick":"🐤","hatching chick":"🐣",
// nature
"sun":"☀️","moon":"🌙","star":"⭐️","cloud":"☁️","umbrella":"☂️","snowflake":"❄️",
"fire":"🔥","droplet":"💧","ocean":"🌊","volcano":"🌋","cactus":"🌵","palm tree":"🌴",
"evergreen tree":"🌲","deciduous tree":"🌳","fallen leaf":"🍂","maple leaf":"🍁",
"seedling":"🌱","flower":"🌸","rose":"🌹","sunflower":"🌻","blossom":"🌼",
//food
"grapes":"🍇","watermelon":"🍉","tangerine":"🍊","banana":"🍌","pineapple":"🍍",
"apple":"🍎","pear":"🍐","peach":"🍑","strawberry":"🍓","cherries":"🍒","mango":"🥭",
"lemon":"🍋","coffee":"☕️","tea":"🍵","beer":"🍺","wine glass":"🍷","cocktail":"🍸",
"tropical drink":"🍹","birthday cake":"🎂","pizza":"🍕","hamburger":"🍔","fries":"🍟",
"hot dog":"🌭","taco":"🌮","burrito":"🌯","popcorn":"🍿","chocolate bar":"🍫",
"candy":"🍬","lollipop":"🍭","honey pot":"🍯"
};
let m;
if (isMathExpression(query)) {
answer = evaluateMathExpression(query);
} else if (query.includes('date') || query.includes('what date is it')) {
@@ -618,17 +878,70 @@ if (isMathExpression(query)) {
const options = { weekday: 'long' };
answer = now.toLocaleDateString(undefined, options);
} else if (query.includes('u are cute') || query.includes('you are cute')) {
answer = "jifshfgdhjf >~< no u"
answer = "jifshfgdhjf >~< no u";
} else if (query.includes('ur cute') || query.includes('your cute')) {
answer = "efkohgefgef >///< no u"
answer = "efkohgefgef >///< no u";
} else if (query.includes('am i cute') || query.includes('am i a cutie?')) {
answer = "yesh :3 u are "
answer = "yesh :3 u are ";
} else if (query.includes('am i a good girl') || query.includes('am i a good boy?')) {
answer = query.includes('girl') ? "yesh :3 u are a good girl" : "yesh :3 u are a good boy";
answer = query.includes('girl')
? "yesh :3 u are a good girl"
: "yesh :3 u are a good boy";
} else if (query.includes('ur hot') || query.includes('you are hot')) {
answer = "jrifyehgyerfgu9wdswgfsafgydwgbfwdfge >~< "
answer = "jrifyehgyerfgu9wdswgfsafgydwgbfwdfge >~< ";
} else if (/^(?:emoji\s+(.+)|(.+)\s+emoji)$/.test(query)) {
const match = query.match(/^(?:emoji\s+(.+)|(.+)\s+emoji)$/);
let name = (match[1] || match[2]).trim().toLowerCase();
const keySpace = name.replace(/_/g, ' ');
const keyUnderscore = name.replace(/\s+/g, '_');
answer = emojiMap[keySpace]
|| emojiMap[keyUnderscore]
|| 'Unknown emoji';
} else if (query.includes('compliment')) {
answer = randomCompliment();
} else if (query.includes('encouragement') || query.includes('encourage me')) {
answer = randomEncouragement();
} else if (query.startsWith('base64 encode ')) {
answer = base64Encode(query.slice(14));
} else if (query.startsWith('base64 decode ')) {
answer = base64Decode(query.slice(14));
} else if ((m = /0x[0-9a-f]+/i.exec(query))) {
answer = hexToDec(m[0]);
} else if (query.match(/\d+\s+to\s+hex/i)) {
answer = decToHex(query.match(/\d+/)[0]);
} else if (query.startsWith('char codes for ')) {
answer = textToCharCodes(query.slice(15));
} else if (query.startsWith('text from codes ')) {
answer = charCodesToText(query.slice(16));
} else if (query.startsWith('word count of ')) {
answer = countWords(query.slice(14)).toString();
} else if (query.startsWith('titlecase ')) {
answer = titleCase(query.slice(10));
} else if (query.startsWith('slugify ')) {
answer = slugify(query.slice(8));
} else if (query.match(/\bprime\b/)) {
answer = isPrime(query.match(/\d+/)[0]) ? 'Yes' : 'No';
} else if (query.includes('gcd of ')) {
const nums = query.match(/\d+/g);
answer = gcd(nums[0], nums[1]).toString();
} else if (query.includes('lcm of ')) {
const nums = query.match(/\d+/g);
answer = lcm(nums[0], nums[1]).toString();
} else if (query.match(/fib(?:onacci)?\s*\d+/i)) {
answer = fibonacci(query.match(/\d+/)[0]).toString();
} else if (query.startsWith('generate password')) {
const n = query.match(/\d+/);
answer = generatePassword(n ? Number(n[0]) : undefined);
} else if (query.includes('days until ')) {
answer = daysUntil(query.split('days until ')[1].trim());
} else if (query.includes('uuid')) {
answer = generateUUID();
} else if (query.startsWith('random string')) {
const n = query.match(/\d+/);
answer = generateRandomString(n ? Number(n[0]) : undefined);
}
const upsellMessages = [
"[new] Try searching 'What's 4+4?'",
"[new] Ask 'What's the date today?'",
@@ -653,32 +966,111 @@ function extractQueryFromUpsellMessage(message) {
%>
%>
<% if (answer) { %>
<div class="container">
<div class="container" style="padding: 1em;">
<h2 style="font-family: 'PokeTube Flex'; font-size: large; text-align: left !important; font-stretch: ultra-expanded; font-weight: 1000; margin-bottom: -0.1em;">
Answer to ur question
</h2>
<h2 style="font-family: 'PokeTube Flex';font-size: large;text-align: right !important;font-stretch: ultra-expanded; font-weight: 1000; margin-bottom: -0.1em;margin-top: -1em;"><i title="PokeInstant Anwser! (not ai/LLM)" class="fa-light fa-sparkles"></i></h2>
<span style="font-size: 7em; margin-bottom: 3em; text-align: left !important; margin-right: 7em;white-space: -moz-pre-wrap !important;white-space: -pre-wrap;white-space: -o-pre-wrap;white-space: pre-wrap;word-wrap: break-word;white-space: -webkit-pre-wrap;word-break: break-all;white-space: normal;"><%= answer %></span>
</div>
<% } else if (showUpsell) { %>
<% } %>
<% if (q == "ai" || q == "aibot" || q == "chatbot") { %>
<div class="container">
<h2 style="font-family: 'PokeTube Flex'; font-size: large; text-align: center !important; font-stretch: ultra-expanded; font-weight: 1000; margin-bottom: -0.1em;">
No, POKE Will Never Have "AI" —Because It Doesn't Need It
<h2 style="font-family: 'PokeTube Flex'; font-size: large; text-align: right !important; font-stretch: ultra-expanded; font-weight: 1000; margin-bottom: -0.1em; margin-top: -1em;">
<i title="PokeIntellagance!" class="fa-light fa-sparkles"></i>
</h2>
<span style="margin-bottom: 3em; text-align: left !important; margin-right: 7em; white-space: pre-wrap; word-break: break-word;">
POKE doesnt need "AI." Theres no good reason for a YouTube front end to include some gimmicky AI assistant. These days, "AI" just means slapping a ChatGPT wrapper on an app, calling it innovative, and hoping no one notices the lack of actual purpose. Its the latest fad, sure, but POKE is about simplicity, privacy, and doing things right—not just joining tech trends for the sake of it.
<br>
POKE is here to make watching videos smoother, not cluttered with unnecessary "features." Real value doesnt come from chasing whatever big tech wants to sell you on. Its about staying true to what actually helps users. So, yeah, no AI here, and honestly, no need for it.
<br>
If u really want a "chatbot" or "ai" : <a href="https://chatgpt.com/"> look at the chatgpt website </a> - (or search using the !ai bang - which is made by duckduckgo, not <a href="https://duckduckgo.com/duckduckgo-help-pages/features/bangs/">us</a>)
</span>
<span
id="answer-text"
style="
display: block;
font-size: 7em;
white-space: pre-wrap;
word-break: break-word;
"
><%= answer %></span>
<div style="display: flex; gap: 1em;">
<button
id="copy-btn"
style="
font-size: 1em;
padding: 0.5em 1em;
cursor: pointer;
background: #222;
color: #fff;
border: none;
border-radius: 0.25em;
"
>Copy</button>
<button
id="ask-btn"
style="font-size:1em; padding:0.5em 1em;"
>Ask ChatGPT</button>
</div>
</div>
<noscript>
<style>
#copy-btn,
#ask-btn {
display: none !important;
}
</style>
</noscript>
<style>
/* Rainbow border keyframes */
@keyframes rainbow-border {
0% { border-color: red; }
16% { border-color: orange; }
32% { border-color: yellow; }
48% { border-color: green; }
64% { border-color: blue; }
80% { border-color: indigo; }
100% { border-color: violet; }
}
#ask-btn {
background: linear-gradient(45deg, #6a11cb, #2575fc);
color: #fff;
border: 2px solid transparent; /* base border */
border-radius: 0.5em;
padding: 0.6em 1.2em;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: transform 0.1s ease, box-shadow 0.2s ease;
}
#ask-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
animation: rainbow-border 2s linear infinite; /* apply rainbow */
}
#ask-btn:active {
transform: translateY(0);
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
}
</style>
<script>
;(function() {
const copyBtn = document.getElementById('copy-btn');
const askBtn = document.getElementById('ask-btn');
const textEl = document.getElementById('answer-text');
copyBtn.addEventListener('click', () => {
const text = textEl.innerText || textEl.textContent;
navigator.clipboard.writeText(text).then(() => {
copyBtn.textContent = 'Copied!';
setTimeout(() => { copyBtn.textContent = 'Copy'; }, 1500);
});
});
askBtn.addEventListener('click', () => {
const userQuery = prompt('What would you like to ask ChatGPT?');
if (userQuery) {
window.location.href = `https://chatgpt.com/?q=${encodeURIComponent(userQuery)}`;
}
});
})();
</script>
<% } else if (showUpsell) { %>
<% } %>
<!-- self harm -->
<%
const searchStrings = [
@@ -1018,12 +1410,12 @@ font-weight: 1000;" href="/watch?v=<%= x.videoId %>" class="title max-lines-2"><
<a href="/channel?id=<%= x.authorId %>"><%= x.author %><% if (x?.authorVerified) { %>
<i class="icon ion ion-md-checkmark-circle" title="verified channel"></i><% } %></a>
<div style="display: inline-flex;flex-direction: row;min-width: 6em;gap: 4px;">
<a href="/download?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 18px;">
<a href="/download?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 8px;">
<div style="display: flex;">
<i class="fa-light fa-download" style="display: flex;font-size: 16px;padding: 5px;border-radius: 18px;margin-left: 2px;"></i><span style="margin-top: 6px;">Download</span>
</div>
</a>
<a href="https://youtube.com/watch?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 18px;height: 2em;width: 12.5em;">
<a href="https://youtube.com/watch?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 8px;height: 2em;width: 12.5em;">
<div style="display: flex;">
<i class="fa-brands fa-youtube" style="display: flex;font-size: 16px;padding: 5px;border-radius: 18px;margin-left: 2px;"></i><span style="margin-top: 6px;">Open on YouTube :/</span>
</div>
@@ -1055,12 +1447,12 @@ font-weight: 1000;" href="/watch?v=<%= x.videoId %>" class="title max-lines-2"><
<i class="icon ion ion-md-checkmark-circle" title="verified channel"></i><% } %></a>
</div>
<div style="display: inline-flex;flex-direction: row;min-width: 6em;gap: 4px;">
<a href="/download?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 18px;">
<a href="/download?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 8px;">
<div style="display: flex;">
<i class="fa-light fa-download" style="display: flex;font-size: 16px;padding: 5px;border-radius: 18px;margin-left: 2px;"></i><span style="margin-top: 6px;">Download</span>
</div>
</a>
<a href="https://youtube.com/watch?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 18px;height: 2em;width: 12.5em;">
<a href="https://youtube.com/watch?v=<%= x.videoId %>" style="background: #333;width: 7.6em;border-radius: 8px;height: 2em;width: 12.5em;">
<div style="display: flex;">
<i class="fa-brands fa-youtube" style="display: flex;font-size: 16px;padding: 5px;border-radius: 18px;margin-left: 2px;"></i><span style="margin-top: 6px;">Open on YouTube :/</span>
</div>

View File

@@ -16,172 +16,6 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses/.
-->
<!doctype html>
<html lang="en">
<head>
<title>PokeTranslate</title>
<link rel="icon" href="/static/yt-ukraine.svg">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="PokeTranslate" property=og:title>
<meta content="Translate text - Anonymously!" property=twitter:description>
<meta content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884" property="og:image" />
<meta content=summary_large_image name=twitter:card>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
<meta name="referrer" content="no-referrer">
<link rel="manifest" href="/manifest.json">
<style>
.center {
text-align: center;
}
.wrap {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 10px 0;
}
.wrap.languages {
margin-bottom: 30px;
}
.item {
width: 100%;
height: 150px;
border-radius: 0.5em;
}
.item-wrapper {
display: flex;
justify-content: center;
width: 450px;
margin: 5px 10px;
}
button,
select,
input,
textarea {
border-radius: 1em;
padding: 10px;
background-color: #131618;
border: 2px solid #495057;
color: #f8f9fa;
}
body {
justify-content: center;
font-family: sans-serif;
background-color: #2c2f33;
color: #f8f9fa;
}
#translation-form {
background-color: #1f2023;
border-radius: 1em;
padding: 20px;
width: 90%;
max-width: 800px;
margin: auto;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
}
h1 {
color: #5bc0de;
font-size: 2rem;
}
#definitions_and_translations {
display: grid;
width: 100%;
grid-template-areas: "definitions translations";
gap: 20px;
}
@media screen and (max-width: 1200px) {
#definitions_and_translations {
display: grid;
grid-template-areas:
"definitions definitions"
"translations translations";
}
}
div.definitions,
div.translations {
padding: 10px;
background-color: #3a3f44;
border-radius: 0.5em;
}
textarea:focus,
input:focus,
button:focus {
border-color: #478061;
outline: 1px solid #478061;
}
a {
color: #599bf6;
}
/* Additional styles to match the calendar page */
header {
margin-bottom: 20px;
}
#switchbutton {
white-space: nowrap;
}
button {
font-size: 1rem;
}
.center button {
margin-top: 15px;
}
/* Responsive design */
@media screen and (max-width: 768px) {
#translation-form {
padding: 10px;
}
.wrap {
flex-direction: column;
}
.item-wrapper {
width: 100%;
}
}
</style>
<% if (isMobile) { %>
<style>
body {
overflow: auto;
}
</style>
<% } %>
<% if (!isMobile) { %>
<style>
body {
overflow: hidden;
}
</style>
<% } %>
</head>
<body>
<div id="translation-form">
<header class="center">
<h1>PokeTranslate</h1>
</header>
<% const languageOptions = [
{ code: 'autodetect', name: 'Autodetect' },
@@ -279,70 +113,284 @@
{ code: 'yo', name: 'Yoruba' },
{ code: 'zu', name: 'Zulu' }
]; %>
<form action="/translate" method="GET" id="translation-form">
<!-- from and to language -->
<div class="wrap languages">
<div class="language">
<!doctype html>
<html lang="en">
<head>
<title>PokeTranslate</title>
<link rel="icon" href="/static/yt-ukraine.svg">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="PokeTranslate" property=og:title>
<meta content="Translate text - Anonymously!" property=twitter:description>
<meta content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884" property="og:image" />
<meta content=summary_large_image name=twitter:card>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
<meta name="referrer" content="no-referrer">
<link rel="manifest" href="/manifest.json">
<link href="https://fonts.bunny.net/css?family=poppins:400,500,600" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
:root {
--accent-1: #ff6b6b;
--accent-2: #f7d794;
--bg-light: #fafafa;
--bg-dark: #202027;
--card-light: rgba(255,255,255,0.55);
--card-dark: rgba(18,18,24,0.55);
--radius: 20px;
--transition: 0.3s ease;
}
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family:'Poppins',sans-serif;
background: var(--bg-light);
color: #fff;
display:flex; align-items:center; justify-content:center;
min-height:100vh; padding:20px;
transition: background var(--transition), color var(--transition);
overflow:hidden;
}
@media (prefers-color-scheme: dark) {
body { background: var(--bg-dark); }
}
.glass {
position: relative;
background: var(--card-light);
backdrop-filter: blur(30px) saturate(200%);
border-radius: var(--radius);
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
width:100%; max-width:840px; overflow:hidden;
transition: background var(--transition), box-shadow var(--transition);
}
@media (prefers-color-scheme: dark) {
.glass {
background: var(--card-dark);
box-shadow: 0 8px 32px rgba(0,0,0,0.7);
}
}
.blob {
position:absolute; width:300px; height:300px;
background: linear-gradient(45deg,var(--accent-1),var(--accent-2));
border-radius: 45% 55% 70% 30%/30% 60% 40% 70%;
animation: blobMove 8s infinite; opacity:0.3; z-index:0;
}
.blob:nth-child(1){ top:-80px; left:-60px; }
.blob:nth-child(2){ bottom:-100px; right:-80px; animation-duration:10s; }
@keyframes blobMove {
0%,100% { transform:translate(0,0) scale(1); }
50% { transform:translate(20px,20px) scale(1.1); }
}
header {
position:relative; padding:28px; text-align:center;
background: linear-gradient(60deg,var(--accent-1),var(--accent-2));
background-size:200% 200%; animation:gradientAnimation 6s ease infinite;
z-index:1;
}
@keyframes gradientAnimation {
0%{ background-position:0% 50%; }
50%{ background-position:100% 50%; }
100%{ background-position:0% 50%; }
}
/* Rainbow title unless user prefers reduced motion */
@media (prefers-reduced-motion: no-preference) {
header h1 {
font-size:2.4rem; font-weight:600; letter-spacing:1.5px;
background: conic-gradient(red,orange,yellow,green,blue,indigo,violet,red);
background-size:300%;
-webkit-background-clip:text;
-webkit-text-fill-color:transparent;
animation: rainbow 4s linear infinite;
}
@keyframes rainbow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
}
@media (prefers-reduced-motion: reduce) {
header h1 {
font-size:2.4rem; font-weight:600; letter-spacing:1.5px;
color:#fff;
}
}
form { position:relative; z-index:2; }
.language-bar { display:flex; align-items:center; gap:12px; padding:16px 24px; }
.language-select { flex:1; }
.language-select select {
width:100%; padding:14px 18px; border-radius:var(--radius);
border:1px solid rgba(255,255,255,0.5); background:transparent;
font-size:1rem; appearance:none; cursor:pointer; color:#fff;
transition:border-color var(--transition);
}
.language-select select:focus { border-color:var(--accent-1); outline:none; }
.swap-button {
font-size:2rem; color:var(--accent-1); text-decoration:none;
transition:transform var(--transition),color var(--transition);
}
.swap-button:hover {
transform:rotate(180deg) scale(1.1);
color:var(--accent-2);
}
.panels {
display:grid; grid-template-columns:1fr 1fr; gap:20px;
padding:0 24px 24px;
}
.panel { position:relative; }
.panel label {
display:block; margin-bottom:8px; font-weight:500; font-size:0.95rem;
color:#fff;
}
.panel textarea {
width:100%; min-height:160px; padding:16px; border-radius:var(--radius);
border:1px solid rgba(255,255,255,0.5); resize:vertical; font-size:1rem;
line-height:1.5; background:rgba(255,255,255,0.2); color:#fff;
transition:border-color var(--transition),box-shadow var(--transition);
}
.panel textarea::placeholder { color:rgba(255,255,255,0.7); }
@media (prefers-color-scheme: dark) {
.panel textarea { background:rgba(20,20,25,0.5); }
}
.panel textarea:focus {
border-color:var(--accent-1);
box-shadow:0 0 8px var(--accent-1);
outline:none;
}
.actions { display:flex; justify-content:center; padding-bottom:24px; }
.actions button {
padding:16px 36px; font-size:1.1rem; font-weight:600; border:none;
border-radius:var(--radius); cursor:pointer;
background:linear-gradient(60deg,var(--accent-1),var(--accent-2));
background-size:200% 200%; animation:gradientAnimation 6s ease infinite;
color:#fff; box-shadow:0 4px 14px rgba(0,0,0,0.2);
transition:transform var(--transition),opacity var(--transition);
}
.actions button:hover { transform:scale(1.03); opacity:0.9; }
@media (max-width:768px) { .panels { grid-template-columns:1fr; } }
</style>
</head>
<body>
<div class="glass">
<div class="blob"></div>
<div class="blob"></div>
<header><h1>PokeTranslate</h1></header>
<form action="/translate" method="GET">
<div class="language-bar">
<div class="language-select">
<select name="from_language" id="from_language">
<% languageOptions.forEach(language => { %>
<option value="<%= language.code %>" <%= language.code === (from_language || 'autodetect') ? 'selected' : '' %>>
<%= language.name %>
<% languageOptions.forEach(lang => { %>
<option value="<%= lang.code %>" <%= lang.code === (from_language||'autodetect')?'selected':''%>>
<%= lang.name %>
</option>
<% }); %>
</select>
</div>
<a
href="?from_language=<%= to_language %>&to_language=<%= from_language %>&input=<%= encodeURIComponent(text) %>"
id="swapBtn"
class="material-icons swap-button"
>swap_horiz</a>
<div class="language-select">
<select name="to_language" id="to_language">
<% languageOptions.slice(1).forEach(language => { %>
<option value="<%= language.code %>" <%= language.code === to_language ? 'selected' : '' %>>
<%= language.name %>
<% languageOptions.slice(1).forEach(lang => { %>
<option value="<%= lang.code %>" <%= lang.code===to_language?'selected':''%>>
<%= lang.name %>
</option>
<% }); %>
</select>
</div>
</div>
<!-- text boxes -->
<div class="wrap">
<div class="item-wrapper">
<textarea autofocus class="item" id="input" name="input" dir="auto" placeholder="<%- text %>">
<%- text %>
</textarea>
<div class="panels">
<div class="panel input">
<label for="input">Input</label>
<textarea id="input" name="input" placeholder="Enter text…"><%= text %></textarea>
</div>
<div class="item-wrapper">
<textarea id="output" class="translation item" dir="auto" placeholder="Translation" readonly>
<%- translation %>
</textarea>
<div class="panel output">
<label for="output">Translation</label>
<textarea id="output" readonly placeholder="Translated text…"><%= translation %></textarea>
</div>
</div>
<div class="center">
<div class="actions">
<button type="submit">Translate :3</button>
</div>
</form>
</div>
<script>
document.getElementById("input").addEventListener("keydown", function (event) {
if (event.keyCode === 13 && (event.metaKey || event.ctrlKey)) {
document.getElementById("translation-form").submit();
(function(){
// swap button
const swapBtn = document.getElementById('swapBtn');
if(swapBtn){
swapBtn.addEventListener('click', function(e){
e.preventDefault();
const from = document.getElementById('from_language');
const to = document.getElementById('to_language');
const inp = document.getElementById('input');
const out = document.getElementById('output');
[from.value, to.value] = [to.value, from.value];
[inp.value, out.value] = [out.value, inp.value];
});
}
// auto-resize all textareas
document.querySelectorAll('textarea').forEach(el=>{
const resize = ()=>{ el.style.height='auto'; el.style.height=el.scrollHeight+'px'; };
el.addEventListener('input', resize);
resize();
});
var input = document.getElementById("input");
var output = document.getElementById("output");
input.setAttribute("style", "height:" + output.scrollHeight + "px;overflow-y:scroll;");
output.setAttribute("style", "height:" + output.scrollHeight + "px;overflow-y:scroll;");
input.addEventListener("input", function (e) {
this.style.height = 150 + "px";
this.style.height = this.scrollHeight + "px";
// live-translate if JS available
if(window.fetch){
const from = document.getElementById('from_language');
const to = document.getElementById('to_language');
const input = document.getElementById('input');
const output= document.getElementById('output');
let timer;
function translateNow(){
const q = new URLSearchParams({
from_language: from.value,
to_language: to.value,
input: input.value
});
fetch(`/translate?${q}`)
.then(r => r.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const newOut = doc.getElementById('output');
if(newOut){
output.value = newOut.value;
output.dispatchEvent(new Event('input'));
}
})
.catch(() => {/* ignore */});
}
// debounce on typing
input.addEventListener('input', ()=>{
clearTimeout(timer);
timer = setTimeout(translateNow, 500);
});
// re-translate on language change
from.addEventListener('change', translateNow);
to.addEventListener('change', translateNow);
}
})();
</script>
<script src="/static/custom-css.js"></script>
</body>
</html>

View File

@@ -12,7 +12,7 @@
if (!isValidYouTubeID(v) || isLetterSpam(v)) {
reason = "Video not found >~<";
} else {
reason = "This helps protect our community. Learn more (TRYING AGAIN....)";
reason = "Poke is currently restarting - please wait 1-2 minutes..";
}
%>

View File

@@ -410,7 +410,7 @@ button:hover, a:hover {
<!-- player -->
<link href="/css/videojs-v8.16.0.css?v=5949545" rel="stylesheet" />
<script src="/static/vjs.min.js?v=45959"></script><!-- custom video player for poke --><script src="/static/player-base.js?v=21_d&lang=en_us&set=player_ias.vflset"> </script>
<script src="/static/vjs.min.js?v=45959"></script><!-- custom video player for poke --><script src="/static/player-base.js?v=565_d&lang=en_us&set=player_ias.vflset"> </script>
<!-- ICONS -->
<!-- Font awesome pro -->
@@ -967,6 +967,7 @@ Offical Discord Server! :3
</a>
</div>
</div>
<a href="/game-hub" class="account" style="margin-right: 4px;"><i class="fa-light fa-gamepad"></i>Games!</a>
<a href="/app" class="account"><i class="fa-light fa-compass"></i>Discover!</a>
</div>
@@ -1024,7 +1025,7 @@ Offical Discord Server! :3
<% if (itag && !qua) { %>
<audio id="aud" preload>
<source src="<%=u%>/latest_version?id=<%=inv_vid.videoId%>&itag=<%=itag%>&local=true" type="video/mp4" onerror="setTimeout(function() { location.reload(); }, 5000);" />
<source src="<%=u%>/latest_version?id=<%=inv_vid.videoId%>&itag=<%=itag%>&local=true" type="video/mp4" />
</audio>
<% } else { %>
<audio id="aud"></audio>
@@ -1038,7 +1039,7 @@ Offical Discord Server! :3
<% } %>
</noscript>
<video poster="<%- media_proxy_url %>/proxy?url=https://i.ytimg.com/vi/<%= inv_vid.videoId %>/hqdefault.jpg" class="video-js player video-ambient-container" id="video" style="border-radius: 16px; box-sizing: border-box; min-width: 100%; display: block;" preload onerror="setTimeout(function() { location.reload(); }, 5000);">
<video poster="<%- media_proxy_url %>/proxy?url=https://i.ytimg.com/vi/<%= inv_vid.videoId %>/hqdefault.jpg" class="video-js player video-ambient-container" id="video" style="border-radius: 16px; box-sizing: border-box; min-width: 100%; display: block;" preload>
<% if (isvidious) { %>
<% if (!qua) { %>
<%
@@ -1049,10 +1050,10 @@ Offical Discord Server! :3
}
});
%>
<source src="<%=u%>/latest_version?id=<%=inv_vid.videoId%>&itag=<%=itag%>&local=true" type="video/mp4; codecs=&quot;avc1.64001F, mp4a.40.2&quot;" label="hd720" selected="true" onerror="setTimeout(function() { location.reload(); }, 5000);">
<source src="<%=u%>/latest_version?id=<%=inv_vid.videoId%>&itag=<%=itag%>&local=true" type="video/mp4; codecs=&quot;avc1.64001F, mp4a.40.2&quot;" label="hd720" selected="true">
<% } %>
<% if (qua === "medium") { %>
<source src="<%=u%>/latest_version?id=<%=inv_vid.videoId%>&itag=18&local=true" type="video/mp4; codecs=&quot;avc1.64001F, mp4a.40.2&quot;" label="sd360" selected="true" onerror="onerror="setTimeout(function() { location.reload(); }, 5000);"">
<source src="<%=u%>/latest_version?id=<%=inv_vid.videoId%>&itag=18&local=true" type="video/mp4; codecs=&quot;avc1.64001F, mp4a.40.2&quot;" label="sd360" selected="true" >
<% } %>
<% } %>
@@ -1783,7 +1784,7 @@ WIP! </a>
</p>
</div>
<% inv.comments.forEach(x =>{ %><div class="fade-in<%- x.commentId %><%- btoa(x.commentId) %>"><div class="_ comment_<%- x.commentId %><%- btoa(btoa(x.commentId)) %>"style="padding:10px"><div class="comment-list left-padding"style="background:#333;padding-top:1px;padding:10px;border-radius:30px;padding-top:0"><div class="d-flex justify-content-between single-comment"style="padding-top:none"><div class="d-flex justify-content-between user"><div class="desc"><h5 style="display:flex;margin-top:7px;padding-top:10px"><div class="thumb"><a href="/channel?id=<%- x.authorId%>@youtube.com"style="width:40px;height:40px"class="avatar"><img loading="lazy"src="<%- media_proxy_url %>/proxy?url=<%= x.authorThumbnails[0].url.replace("https://yt3.ggpht.com/", "https://vid.puffyan.us/ggpht/") %>"></a></div><% if (!x.authorIsChannelOwner) { %><p class="comments-author"><a href="/channel?id=<%- x.authorId%>"style="color:var(--text-color);text-decoration:none"><%- x.author%><% if (x.verified) { %><i class="icon ion ion-md-checkmark-circle"></i><% } %><p class="date-publish"><%- x.publishedText %></p></a></p><% } %><% if (x.authorIsChannelOwner) { %><p class="comments-author owner"><a href="/channel?id=<%- x.authorId%>@youtube.com"style="color:var(--text-color);text-decoration:none"><%- x.author%><% if (x.verified) { %><i class="icon ion ion-md-checkmark-circle"></i><% } %><p class="date-publish"><%- x.publishedText %></p></a><% } %></h5><p class="comment"style="font-weight:700"><%- x.contentHtml.replace(/\n/g, "<br>"); %><br><br><% if (x.likeCount === 0) { %><i class="fa-light fa-thumbs-up"></i> | <i class="fa-light fa-thumbs-down"></i><% } else { %><i class="fa-light fa-thumbs-up"></i> <%= convert(x.likeCount) %> | <i class="fa-light fa-thumbs-down"></i><% } %><% if(x.creatorHeart) { %> <i class="icon creator-heart-small-container ion-ios-heart"style="color:#ffabcc"title="<%= x.creatorHeart.creatorName%> marked it with a ❤ owo"></i><% } %></div></div></div></div></div></div><% }) %>
<% inv.comments.forEach(x =>{ %> <div class="fade-in<%- x.commentId %><%- btoa(x.commentId) %>"><div class="_ comment_<%- x.commentId %><%- btoa(btoa(x.commentId)) %>" style="padding:10px"><div class="comment-list left-padding" style="background:#333;padding-top:1px;padding:10px;border-radius:30px;padding-top:0"><div class="d-flex justify-content-between single-comment" style="padding-top:none"><div class="d-flex justify-content-between user"><div class="desc"><h5 style="display:flex;margin-top:7px;padding-top:10px"><div class="thumb"><a href="/channel?id=<%- x.authorId%>@youtube.com" style="width:40px;height:40px" class="avatar"><img loading="lazy" src="<%- media_proxy_url %>/proxy?url=<%= x.authorThumbnails[0].url.replace("https://yt3.ggpht.com/", "https://vid.puffyan.us/ggpht/") %>"></a></div> <% if (!x.authorIsChannelOwner) { %> <p class="comments-author"><a href="/channel?id=<%- x.authorId%>" style="color:var(--text-color);text-decoration:none"> <%- x.author%><% if (x.verified) { %><i class="icon ion ion-md-checkmark-circle"></i><% } %> <p class="date-publish"><%- x.publishedText %></p></a></p> <% } %><% if (x.authorIsChannelOwner) { %> <p class="comments-author owner"><a href="/channel?id=<%- x.authorId%>@youtube.com" style="color:var(--text-color);text-decoration:none"> <%- x.author%><% if (x.verified) { %><i class="icon ion ion-md-checkmark-circle"></i><% } %> <p class="date-publish"><%- x.publishedText %></p></a> <% } %> </p></h5><p class="comment" style="font-weight:700"> <%- x.contentHtml.replace(/\n/g, "<br />"); %><br><br> <% if (x.likeCount === 0) { %><i class="fa-light fa-thumbs-up"></i>|<i class="fa-light fa-thumbs-down"></i><% } else { %><i class="fa-light fa-thumbs-up"></i> <%= convert(x.likeCount) %> |<i class="fa-light fa-thumbs-down" style="margin-right: 5px;"></i><% } %> <% if (x.replies?.replyCount != 0) { %> <i class="fa-light fa-reply"></i> <%- x.replies?.replyCount || "0" %> <% } %> <% if(x.creatorHeart) { %> <i class="icon creator-heart-small-container ion-ios-heart" style="color:#ffabcc" title="<%= x.creatorHeart.creatorName%> marked it with a ❤ owo"></i><% } %> </p></div></div></div></div></div></div> <% }) %>
<center>
<a href="#top">Go To Top</a>
</center>
@@ -1837,12 +1838,6 @@ WIP! </a>
<input title="autoplay the next video" name="continue" id="continue" type="checkbox" >
</div>
<div style="text-align: right;margin-top: -2.2em;width: 1em; margin-left: auto; margin-right: 5px;" >
<a title="Upload content :3" href="/video/upload">
<i class="fa-light fa-circle-plus"></i>
</a>
</div>
<% if (!f) { %>
@@ -2542,8 +2537,16 @@ a {
<style>body, html { margin: 0; padding: 0; } * { font-family: ubuntu; color:#fff } .player { background-color: #000 !important; display: grid; grid-template-columns: 1fr min-content; grid-template-rows: max-content 1fr max-content max-content max-content; gap: 0 0; width: 100%; /* height: 100%; */ aspect-ratio: 16 / 9; } .player * { color: #fff; } .player.embed, video.embed { position: fixed; top: 0; bottom: 0; left: 0; right: 0; } .player * { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .player > video { position: relative; width: 100%; height: 100%; z-index: 0; grid-area: 1 / 1 / 6 / 3; } .player.hide-controls > .player-title, .player.hide-controls > .player-controls, .player.hide-controls > .player-playback-bar-container, .player.hide-controls > .player-menu { display: none !important; } .player-controls { background-color: #0007; display: flex; justify-content: center; align-items: center; z-index: 1; grid-area: 1 / 1 / 6 / 3; } .player-button { width: 96px; height: 96px; font-size: 90px; text-align: center; line-height: 48px; } .player-tiny-button { width: 40px; font-size: 20px; text-align: center; } .player-tiny-button > i { color: #ddd; } .player-button, .player-button * { color: #dddddd !important; text-decoration: none; } .player-button > i { min-width: 48px; } .player-button:hover, .player-button:hover * { color: #fff !important; } .player-playback-bar { transition: width linear 100ms; } .player-playback-bar-container { grid-area: 4 / 1 / 5 / 3; display: flex; column-gap: 8px; justify-content: center; align-items: center; height: 8px; transition: height linear 100ms; width: 100%; z-index: 2; margin-bottom: 10px; } .player-playback-bar-bg { background-color: #fff3 !important; width: 100%; height: 100%; z-index: 2; display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr; } .player-playback-bar-bg > * { grid-area: 1 / 1 / 2 / 2; } .player-playback-bar-buffer { background-color: #fffa !important; height: 100%; width: 0; z-index: 3; } .player-playback-bar-fg { background-color: #f00 !important; height: 100%; width: 0; z-index: 4; } .player-buffering { grid-area: 1 / 1 / 6 / 3; z-index: 1; display: flex; justify-content: center; align-items: center; } .player-buffering-spinner { width: 80px; height: 80px; }</style>
<body>
<div class="app" style="color:#fff;margin-top: 0;">
<nav>
<div class="left"><a class="class" href="/" style="font-family:Inter,sans-serif;color:#fff"> <img style="width: 8.5em;display: block;margin-left: -1.5em;margin-right: auto;" src="/css/logo-poke.svg"> </a>
<nav style="
height: 2em;
margin-bottom: 1em;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 12px;
">
<div class="left"><a class="class" href="/" style="font-family:Inter,sans-serif;color:#fff">
</a>
</div>
<div class="middle">
@@ -2575,12 +2578,6 @@ a {
<% } %>
<% if (qua === "medium") { %>
<source src="https://eu-proxy.poketube.fun/latest_version?id=<%=inv_vid.videoId%>&itag=18&local=true" type="video/mp4; codecs=&quot;avc1.64001F, mp4a.40.2&quot;" label="sd360" selected="true">
<% } %>
@@ -2921,6 +2918,83 @@ font-size: 13px;margin:0;padding:0;white-space: nowrap;
const userScoreColor = userScore >= 80 ? 'green' : userScore >= 50 ? 'orange' : 'red';
%>
<div class="nerddd" style="background:#272727;padding: 5px;margin-top: 12px;border-radius: 11px;font-family: 'PokeTube Flex';font-stretch: extra-expanded;font-weight: 700;">
<style>
@keyframes gradientAnimation {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes glow {
0%, 100% {
box-shadow: 0 0 15px rgba(0,255,255,0.7), inset 0 0 10px rgba(0,255,255,0.3);
}
50% {
box-shadow: 0 0 25px rgba(0,255,255,1), inset 0 0 15px rgba(0,255,255,0.5);
}
}
.ai-buttona {
padding: 0.75rem 1.5rem;
font-size: 15px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #ffffff;
text-decoration: none;
background: linear-gradient(
90deg,
hsla(235, 21%, 21%, 1) 0%,
hsla(194, 41%, 22%, 1) 50%,
hsla(174, 48%, 20%, 1) 100%
);
background-size: 238% 300%;
border-radius: 2rem;
border: none;
box-shadow: 0 0 15px rgba(0,255,255,0.7), inset 0 0 10px rgba(0,255,255,0.3);
transition: box-shadow 0.2s ease;
margin-top: 1em;
margin-bottom: -1em;
margin-left: 6em;
text-align: center;
animation: none;
}
.ai-button:hover {
box-shadow: 0 0 20px rgba(0,255,255,1), inset 0 0 15px rgba(0,255,255,0.5);
opacity: 0.9;
}
</style>
<%
const variant = Math.random() < 0.5 ? 'A' : 'B';
const showButton = Math.random() < 0.5;
const desc = inv_vid.description || '';
%>
<% if (showButton) { %>
<br><br> <a
href="https://chatgpt.com/?q=<%= encodeURIComponent(
desc +
' Please summarize the selection using precise and concise language. ' +
'Use headers and bulleted lists in the summary, to make it scannable. ' +
'Maintain the meaning and factual accuracy.'
) %>"
target="_blank"
rel="noopener noreferrer"
aria-label="Summarise the video description"
class="ai-buttona"
data-variant="<%= variant %>"
>
Summarise!
</a>
<% } %>
<br><br><br>
<%-String(channelurlfixer(inv_vid.descriptionHtml)).replace(/\n/g, " <br> ").replace(/twitter\.com/g, "twitter.com").replace(/reddit\.com/g, "redlib.matthew.science") %>
@@ -3479,7 +3553,62 @@ More Epic options owo~
// @license-end
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const video = document.querySelector('video');
if (!video) return;
if ('mediaSession' in navigator) {
// Set metadata
navigator.mediaSession.metadata = new MediaMetadata({
title: "<%- inv_vid.title %>",
artist: "<%= video?.Channel?.Name || k.Video.Channel.Name %>",
album: "Poke",
artwork: [
{
src: "https://i.ytimg.com/vi/<%=inv_vid.videoId%>/maxresdefault.jpg",
sizes: "1280x720",
type: "image/jpeg"
}
]
});
// Update playback state
const updatePositionState = () => {
if ('setPositionState' in navigator.mediaSession) {
navigator.mediaSession.setPositionState({
duration: video.duration || 0,
playbackRate: video.playbackRate || 1,
position: video.currentTime || 0
});
}
};
video.addEventListener('timeupdate', updatePositionState);
video.addEventListener('ratechange', updatePositionState);
video.addEventListener('loadedmetadata', updatePositionState);
// Media control handlers
navigator.mediaSession.setActionHandler('play', () => {
video.play();
});
navigator.mediaSession.setActionHandler('pause', () => {
video.pause();
});
navigator.mediaSession.setActionHandler('seekto', (details) => {
if (details.fastSeek && 'fastSeek' in video) {
video.fastSeek(details.seekTime);
} else {
video.currentTime = details.seekTime;
}
updatePositionState();
});
}
});
</script>
<script>
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-3.0-or-later

View File

@@ -35,7 +35,7 @@
"activitypub-express": "^4.4.1",
"duck-duck-scrape": "^2.2.5",
"google-it": "^1.6.4",
"youtubei.js": "^9.3.0"
"youtubei.js": "^13.4.0"
},
"engines": {
"node": ">=18"

View File

@@ -23,7 +23,6 @@ var ping = require("ping");
const sha384 = modules.hash;
const splash = [
"Woke!",
"Gay gay homosexaul gay!",
@@ -131,10 +130,7 @@ const splash = [
"stallmansupport.org!!!",
"does include nya~!!!",
"actually stable! :3",
]
];
function getJson(str) {
try {
@@ -153,13 +149,16 @@ module.exports = function (app, config, renderTemplate) {
tab = `/?type=${capitalizeFirstLetter(req.query.tab)}`;
}
const invtrend = await fetch(
`${config.invapi}/trending${tab}`
);
const invtrend = await fetch(`${config.invapi}/trending${tab}`, {
headers: { "User-Agent": config.useragent },
});
const t = getJson(await invtrend.text());
const invpopular = await fetch(
`https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/v1/popular`
`https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/v1/popular`,
{
headers: { "User-Agent": config.useragent },
}
);
const p = getJson(await invpopular.text());
@@ -190,18 +189,15 @@ module.exports = function (app, config, renderTemplate) {
const uaos = req.useragent.os;
const random = splash[Math.floor(Math.random() * splash.length)];
const browser = req.useragent.browser;
const isOldWindows = (uaos === "Windows 7" || uaos === "Windows 8") && browser === "Firefox";
const isOldWindows =
(uaos === "Windows 7" || uaos === "Windows 8") &&
browser === "Firefox";
var proxyurl = config.p_url;
const secure = [
"poketube.fun",
"localhost" //
].includes(req.hostname);
const verify = [
"poketube.fun",
"poke.ashley0143.xyz",
"localhost"
].includes(req.hostname);
const secure = ["poketube.fun", "localhost"].includes(req.hostname);
const verify = ["poketube.fun", "poke.ashley0143.xyz", "localhost"].includes(
req.hostname
);
const rendermainpage = () => {
if (req.useragent.isMobile) {
@@ -216,7 +212,7 @@ module.exports = function (app, config, renderTemplate) {
verify,
isOldWindows,
proxyurl,
random
random,
});
};
@@ -225,10 +221,10 @@ module.exports = function (app, config, renderTemplate) {
if (isvld && req.params.v.length >= 10) {
return res.redirect(`/watch?v=${req.params.v}`);
} else {
res.status(404)
res.status(404);
return renderTemplate(res, req, "404.ejs", {
isOldWindows,
random
random,
});
}
}

View File

@@ -1,22 +1,5 @@
const {
fetcher,
core,
wiki,
musicInfo,
modules,
version,
initlog,
init,
} = require("../libpoketube-initsys.js");
const {
IsJsonString,
convert,
getFirstLine,
capitalizeFirstLetter,
turntomins,
getRandomInt,
getRandomArbitrary,
} = require("../ptutils/libpt-coreutils.js");
const { modules } = require("../libpoketube-initsys.js");
var http = require("https");
var ping = require("ping");

View File

@@ -1,22 +1,4 @@
const {
fetcher,
core,
wiki,
musicInfo,
modules,
version,
initlog,
init,
} = require("../libpoketube-initsys.js");
const {
IsJsonString,
convert,
getFirstLine,
capitalizeFirstLetter,
turntomins,
getRandomInt,
getRandomArbitrary,
} = require("../ptutils/libpt-coreutils.js");
const { modules, version } = require("../libpoketube-initsys.js");
function getJson(str) {
try {
@@ -29,29 +11,35 @@ function getJson(str) {
const pkg = require("../../../package.json");
const os = require('os');
const cnf = require("../../../config.json");
const innertube = require("../libpoketube-youtubei-objects.json");
const { execSync } = require('child_process');
const verfull = "v24.1906-sho-MAJOR_UPDATE-stable-dev-nonLTS-git-MTcxODc5NDY3NQ==";
const versmol = "v24.1906-sho"
const { execSync } = require('child_process'); // DO NOT ABBRV THIS :SOB:
const verfull = "v25.2705-luna-MAJOR_UPDATE-stable-dev-nonLTS-git-MTc0NTcwNjc4MA==";
const versmol = "v25.2705-luna";
const branch = "dev/master";
const codename = "sho";
const versionnumber = "293";
const relaseunixdate = "MTcxODc5NDY3NQ=="
const updatequote = "pls fund vennie plush -Bims"
const codename = "luna";
const versionnumber = "294";
const relaseunixdate = "MTc0NTcwNjc4MA==";
const updatequote = "i created this world.....to feel some control...";
module.exports = function (app, config, renderTemplate) {
const headers = {
'User-Agent': config.useragent,
};
app.get("/vi/:v/:t", async function (req, res) {
var url = `https://i.ytimg.com/vi/${req.params.v}/${req.params.t}`
var url = `https://i.ytimg.com/vi/${req.params.v}/${req.params.t}`;
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/avatars/:v", async function (req, res) {
@@ -59,6 +47,7 @@ app.get("/avatars/:v", async function (req, res) {
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
@@ -69,39 +58,30 @@ app.get("/avatars/:v", async function (req, res) {
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/avatars/ytc/:v", async function (req, res) {
var url = `https://vid.puffyan.us/ggpht/ytc/${req.params.v.replace("ytc", "")}`;
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/api/search", async (req, res) => {
const query = req.query.query;
if (!query) {
return res.redirect("/");
}
return res.redirect(`/search?query=${query}`);
});
app.get("/api/video/download", async function (req, res) {
var v = req.query.v;
var q = "18";
if (req.query.q) q = req.query.q;
const url = `https://eu-proxy.poketube.fun/latest_version?id=${v}&itag=${q}&local=true`;
const url = `${config.videourl}/latest_version?id=${v}&itag=${q}&local=true`;
res.redirect(url);
});
@@ -113,9 +93,12 @@ app.get("/avatars/:v", async function (req, res) {
const l = req.query.h;
try {
let url = `https://eu-proxy.poketube.fun/api/v1/captions/${id}?label=${l}`;
let url = `${config.videourl}/api/v1/captions/${id}?label=${l}`;
let f = await fetch(url, {
headers: headers,
});
let f = await fetch(url);
const body = await f.text();
res.send(body);
@@ -131,7 +114,9 @@ app.get("/avatars/:v", async function (req, res) {
if (id) {
const apiUrl = `https://ryd-proxy.kavin.rocks/votes/${id}&hash=d0550b6e28c8f93533a569c314d5b4e2`;
const response = await fetch(apiUrl);
const response = await fetch(apiUrl, {
headers: headers,
});
if (response.status === 400) {
const error = await response.json();
@@ -224,48 +209,6 @@ app.get("/avatars/:v", async function (req, res) {
}
});
app.use("/sb/i/:v/:imagePath/:img", async function (req, res) {
const { v, imagePath, img } = req.params;
const { sqp, xywh } = req.query;
const sighMatch = req.url.match(/&amp;sigh=([^&#]+)/);
const sigh = sighMatch ? sighMatch[1] : undefined;
const url = `https://yt.miruku.cafe/sb/i/${v}/${imagePath}/${img}?sqp=${sqp}&sigh=${sigh}&xywh=${req.query.xywh}`;
try {
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
});
f.body.pipe(res);
console.log(url)
} catch (error) {
console.error("Error fetching image:", error);
res.status(500).send("Error fetching image");
}
});
app.get("/api/storyboards", async (req, res) => {
const { fetch } = await import("undici");
const id = req.query.v;
const l = req.query.h;
try {
let url = `https://yt.miruku.cafe/api/v1/storyboards/${id}?width=320&height=180`;
let f = await fetch(url);
let body = await f.text();
body = body.replace(/#xywh=(\d+),(\d+),(\d+),(\d+)/g, (match, x, y, w, h) => {
return `&xywh=${x},${y},${w},${h}`;
});
res.send(body);
} catch {}
});
app.get("/feeds/videos.xml", async (req, res) => {
const id = req.query.channel_id;
@@ -273,6 +216,7 @@ app.use("/sb/i/:v/:imagePath/:img", async function (req, res) {
let f = await modules.fetch(url, {
method: req.method,
headers: headers, // Add headers to the fetch request
});
f.body.pipe(res);
@@ -285,13 +229,12 @@ app.use("/sb/i/:v/:imagePath/:img", async function (req, res) {
let f = await modules.fetch(url, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/api/redirect", async (req, res) => {
const red_url = atob(req.query.u);
@@ -314,7 +257,9 @@ app.use("/sb/i/:v/:imagePath/:img", async function (req, res) {
let latestCommitHash;
const invidious = await modules
.fetch("https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/v1/stats")
.fetch("https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/v1/stats", {
headers: headers,
})
.then((res) => res.text())
.then((txt) => getJson(txt));
@@ -330,19 +275,20 @@ execSync('git rev-list HEAD -n 1 --abbrev-commit', (error, stdout, stderr) => {
latestCommitHash = stdout.trim();
});
const { useragent, ...configWithoutUA } = cnf;
const response = {
pt_version: {
version: versmol,
version_full: verfull,
commit: latestCommitHash
commit: latestCommitHash,
},
branch,
updatequote,
relaseunixdate,
vernum: versionnumber,
codename,
config:cnf,
config: configWithoutUA,
system: {
ram: `${roundedMemory} GB`,
cpu: cpus[0].model,
@@ -374,7 +320,9 @@ execSync('git rev-list HEAD -n 1 --abbrev-commit', (error, stdout, stderr) => {
try {
const url = `https://raw.githubusercontent.com/ashley0143/poke/main/instances.json`;
let f = await fetch(url)
let f = await fetch(url, {
headers: headers,
})
.then((res) => res.text())
.then((json) => JSON.parse(json));

View File

@@ -135,7 +135,6 @@ module.exports = function (app, config, renderTemplate) {
res.redirect("https://lite.duckduckgo.com/lite/?q=" + query);
}
if (query && query.startsWith("Hey ChatGPT,") && query.length > 2) {
res.redirect("https://chatgpt.com/?q=" + query + "- sent using pokeAI features");
}
@@ -160,11 +159,14 @@ if (req.query.from === 'hashtag') {
searchUrl = `${config.invapi}/search?q=${encodeURIComponent(query)}&page=${encodeURIComponent(continuation)}&date=${date}&type=${type}&duration=${duration}&sort=${sort}&hl=en+gb`;
}
const xmlData = await fetch(searchUrl)
const xmlData = await fetch(searchUrl, {
headers: {
'User-Agent': config.useragent,
},
})
.then((res) => res.text())
.then((txt) => getJson(txt));
renderTemplate(res, req, "search.ejs", {
invresults: xmlData,
turntomins,
@@ -218,7 +220,11 @@ const xmlData = await fetch(searchUrl)
try {
// about
const bout = await fetch(config.tubeApi + `channel?id=${ID}&tab=about`);
const bout = await fetch(config.tubeApi + `channel?id=${ID}&tab=about`, {
headers: {
'User-Agent': config.useragent,
},
});
const h = await bout.text();
var boutJson = JSON.parse(modules.toJson(h));
} catch {
@@ -238,7 +244,11 @@ const xmlData = await fetch(searchUrl)
const getChannelData = async (url) => {
try {
return await fetch(url)
return await fetch(url, {
headers: {
'User-Agent': config.useragent,
},
})
.then((res) => res.text())
.then((txt) => getJson(txt));
} catch (error) {
@@ -280,7 +290,6 @@ const PlaylistUrl = `${apiUrl}${ID}/${atob(
getChannelData(channelINVUrl),
]);
var bannedchannels = ["UC1okSIA8UEY8OqvtjGHFvzA", "UClsVg5LkK2COQRo1mVS4taA", "UCIr4vkCsn0tdTW2xZ1jRG1g"];
var bypassQuery = "cG9rZXR1YmVjaGFubmVsYnlwYXNzbG9scGVvcGxldGhpbmt0aGlzaXNjZW5zb3JzaGlwLTQ1OTBh";
@@ -288,14 +297,11 @@ var bypassExists = req.query.bypass === bypassQuery;
var tabExists = 'tab' in req.query;
var continuationExists = 'continuation' in req.query;
if (bannedchannels.some(channel => channel === ID) && !bypassExists && !tabExists && !continuationExists) {
var cinv = {
error: `this channel may include disinformation. If you still wanna view content <a href="/channel?id=${ID}&bypass=${bypassQuery}">click here</a> to bypass this restriction.`
};
}
}
function getThumbnailUrl(video) {
const maxresDefaultThumbnail = video.videoThumbnails.find(
@@ -333,8 +339,6 @@ if (bannedchannels.some(channel => channel === ID) && !bypassExists && !tabExist
);
const dnoreplace = about?.Description.toString();
let ChannelFirstVideoObject = {
subCountText: "0",
authorVerified: false,
@@ -363,8 +367,7 @@ if (bannedchannels.some(channel => channel === ID) && !bypassExists && !tabExist
isMobile: req.useragent.isMobile,
about,
playlist,
subs:
typeof subscribers === "string"
subs: typeof subscribers === "string"
? subscribers.replace("subscribers", "")
: "None",
desc: dnoreplace === "[object Object]" ? "" : description,

View File

@@ -82,23 +82,23 @@ module.exports = function (app, config, renderTemplate) {
renderTemplate(res, req, "rewind.ejs");
});
app.get("/notepad", function (req, res) {
renderTemplate(res, req, "pokepad.ejs");
});
app.get("/translate", async function (req, res) {
const { fetch } = await import("undici");
const api_url = "https://simplytranslate.org/api/translate";
// Fetch translation data
const translationResponse = await fetch(
`${api_url}?from=${req.query.from_language}&to=${req.query.to_language}&text=${req.query.input}&engine=google`
);
// Check if the request was successful (status code 200)
const translationData = await translationResponse.json();
// Extract translated_text from the response
const translatedText = translationData.translated_text;
// Render the template with the translated text
renderTemplate(res, req, "translate.ejs", {
translation: translatedText,
text: req.query.input || "enter text here",
@@ -115,27 +115,22 @@ module.exports = function (app, config, renderTemplate) {
app.get("/apps", function (req, res) {
renderTemplate(res, req, "apps.ejs");
});
const headers = { "User-Agent": config.useragent };
app.get("/playlist", async function (req, res) {
const { fetch } = await import("undici");
if (!req.query.list) res.redirect("/");
if (req.useragent.isMobile) res.redirect("/");
const playlist = await fetch(
`${config.invapi}/playlists/${req.query.list}?hl=en-us`
);
const playlist = await fetch(`${config.invapi}/playlists/${req.query.list}?hl=en-us`, { headers });
const p = getJson(await playlist.text());
var mediaproxy = config.media_proxy;
if (req.useragent.source.includes("Pardus")) {
var mediaproxy = "https://media-proxy.ashley0143.xyz";
mediaproxy = "https://media-proxy.ashley0143.xyz";
}
renderTemplate(res, req, "playlist.ejs", { p, mediaproxy });
});
renderTemplate(res, req, "playlist.ejs", {
p,
mediaproxy,
});
});
app.get("/license", function (req, res) {
renderTemplate(res, req, "license.ejs");
@@ -261,7 +256,7 @@ app.get('/calendar', (req, res) => {
});
app.get("/game-hub", function (req, res) {
var gameslist = ["pong", "tic-tac-toe", "sudoku", "snake"];
var gameslist = ["pong", "tic-tac-toe", "sudoku", "snake", "breakout", "minesweeper"];
var requestedGame = req.query.game;
if (req.query.game && !gameslist.includes(requestedGame)) {

View File

@@ -452,7 +452,7 @@ module.exports = function (app, config, renderTemplate) {
var vidurl = u.losslessurl;
}
var vidurl = "https://eu-proxy.poketube.fun";
var vidurl = config.videourl;
var isvidious = true;
if (req.useragent.source.includes("Pardus")) {

View File

@@ -12,20 +12,7 @@ const getdislikes = require("../libpoketube/libpoketube-dislikes.js");
const getColors = require("get-image-colors");
const config = require("../../config.json")
/**
* Class representing PokeTube's core functionality.
*/
class InnerTubePokeVidious {
/**
* Create an instance of InnerTubePokeVidious.
* @param {object} config - Configuration object for InnerTubePokeVidious.
* @param {string} config.tubeApi - Tube API URL.
* @param {string} config.invapi - Invid API URL.
* @param {string} config.invapi_alt - Invid API URL - ALT .
* @param {string} config.dislikes - Dislikes API URL.
* @param {string} config.t_url - Matomo URL.
*/
constructor(config) {
this.config = config;
this.cache = {};
@@ -34,19 +21,14 @@ class InnerTubePokeVidious {
this.param_legacy = "CgIIAdgDAQ%3D%3D"
this.apikey = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
this.ANDROID_API_KEY = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w"
this.ANDROID_APP_VERSION = "19.14.42"
this.ANDROID_VERSION = "13"
this.useragent = "com.google.android.youtube/19.14.42 (Linux; U; Android 12; US) gzip"
this.ANDROID_APP_VERSION = "20.20.41" // https://www.apkmirror.com/apk/google-inc/youtube/youtube-20-20-41-release/
this.ANDROID_VERSION = "16" // https://en.wikipedia.org/wiki/Android_version_history
this.useragent = config.useragent || "PokeTube/2.0.0 (GNU/Linux; Android 14; Trisquel 11; poketube-vidious; like FreeTube)"
this.INNERTUBE_CONTEXT_CLIENT_VERSION = "1"
this.region = "region=US";
this.sqp = "-oaymwEbCKgBEF5IVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLBy_x4UUHLNDZtJtH0PXeQGoRFTgw";
}
/**
* Fetch JSON from API response.
* @param {string} str - String response from the API.
* @returns {object|null} Parsed JSON object or null if parsing failed.
*/
getJson(str) {
try {
return JSON.parse(str);
@@ -55,92 +37,68 @@ class InnerTubePokeVidious {
}
}
/**
* Check if the provided object has the required properties.
* @param {object} obj - Object to check.
* @returns {boolean} True if the object has the required properties, false otherwise.
*/
checkUnexistingObject(obj) {
if (obj) {
if ("authorId" in obj) {
return true;
}
}
return obj && "authorId" in obj;
}
/**
* Fetch video information.
* @param {string} v - Video ID.
* @returns {Promise<object>} Promise resolving to the video information.
*/
async getYouTubeApiVideo(f, v, contentlang, contentregion) {
const { fetch } = await import("undici");
if (v == null) return "Gib ID";
// Check if result is already cached
if (this.cache[v] && Date.now() - this.cache[v].timestamp < 3600000) {
return this.cache[v].result;
}
const headers = {};
let desc = "";
const headers = {
"User-Agent": this.useragent,
};
const fetchWithRetry = async (url, options = {}, retries = 3) => {
for (let attempt = 0; attempt < retries; attempt++) {
const res = await fetch(url, options);
if (res.status === 500 && attempt < retries - 1) {
continue; // retry on 500
const res = await fetch(url, {
...options,
headers: {
...options.headers,
...headers,
}
});
if (res.status === 500 && attempt < retries - 1) continue;
return res;
}
// If all retries fail, return last response
return null;
};
try {
const [invComments, videoInfo, videoData] = await Promise.all([
fetchWithRetry(`${this.config.invapi}/comments/${v}?hl=${contentlang}&region=${contentregion}&h=${btoa(Date.now())}`).then((res) => res.text()),
fetchWithRetry(`${this.config.invapi}/videos/${v}?hl=${contentlang}&region=${contentregion}&h=${btoa(Date.now())}`).then((res) => res.text()),
curly
.get(`${this.config.tubeApi}video?v=${v}`, {
fetchWithRetry(`${this.config.invapi}/comments/${v}?hl=${contentlang}&region=${contentregion}&h=${btoa(Date.now())}`).then(res => res.text()),
fetchWithRetry(`${this.config.invapi}/videos/${v}?hl=${contentlang}&region=${contentregion}&h=${btoa(Date.now())}`).then(res => res.text()),
curly.get(`${this.config.tubeApi}video?v=${v}`, {
httpHeader: Object.entries(headers).map(([k, v]) => `${k}: ${v}`),
})
.then((res) => {
}).then(res => {
const json = toJson(res.data);
const video = this.getJson(json);
return { json, video };
}),
]);
const comments = await this.getJson(invComments);
const vid = await this.getJson(videoInfo);
const comments = this.getJson(invComments);
const vid = this.getJson(videoInfo);
const { json, video } = videoData;
var channel_uploads = { };
if (f == "true") {
channel_uploads = await fetch(
`${this.config.invapi}/channels/${vid.authorId}?hl=${contentlang}&region=${contentregion}`
);
var p = this.getJson(await channel_uploads.text());
let p = {};
if (f === "true") {
const uploads = await fetchWithRetry(`${this.config.invapi}/channels/${vid.authorId}?hl=${contentlang}&region=${contentregion}`);
p = this.getJson(await uploads.text());
}
if (!vid) {
console.log(
`Sorry nya, we couldn't find any information about that video qwq`
);
console.log(`Sorry nya, we couldn't find any information about that video qwq`);
}
if (this.checkUnexistingObject(vid)) {
const fe = await getdislikes(v);
try {
const headers = {};
// Store result in cache
this.cache[v] = {
result: {
json: json?.video,
@@ -151,59 +109,38 @@ class InnerTubePokeVidious {
engagement: fe.engagement,
wiki: "",
desc: "",
color: await getColors(
`https://i.ytimg.com/vi/${v}/hqdefault.jpg?sqp=${this.sqp}`
).then((colors) => colors[0].hex()),
color2: await getColors(
`https://i.ytimg.com/vi/${v}/hqdefault.jpg?sqp=${this.sqp}`
).then((colors) => colors[1].hex()),
color: await getColors(`https://i.ytimg.com/vi/${v}/hqdefault.jpg?sqp=${this.sqp}`).then(colors => colors[0].hex()),
color2: await getColors(`https://i.ytimg.com/vi/${v}/hqdefault.jpg?sqp=${this.sqp}`).then(colors => colors[1].hex()),
},
timestamp: Date.now(),
};
return this.cache[v].result;
}
} catch (error) {
this.initError("Error getting video", error);
}
}
} catch {
}
}
/**
* Check if a video ID is valid.
* @param {string} v - Video ID.
* @returns {boolean} True if the video ID is valid, false otherwise.
*/
isvalidvideo(v) {
if (v != "assets" && v != "cdn-cgi" && v != "404") {
const regex = new RegExp("^([a-zA-Z0-9_-]{11})");
const isMatch = regex.test(v);
return isMatch;
} else {
return /^([a-zA-Z0-9_-]{11})/.test(v);
}
return false;
}
}
/**
* Initialize an error.
* @param {string} args - Error message.
* @param {Error} error - Error object.
*/
initError(args, error) {
console.error("[LIBPT CORE ERROR] " + args, error);
}
}
// Create an instance of InnerTubePokeVidious with the provided config
const pokeTubeApiCore = new InnerTubePokeVidious({
tubeApi: "https://inner-api.poketube.fun/api/",
invapi: "https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/v1",
invapi_alt: config.proxylocation === "EU" ? "https://invid-api.poketube.fun/api/v1" : "https://iv.ggtyler.dev/api/v1",
dislikes: "https://returnyoutubedislikeapi.com/votes?videoId=",
t_url: "https://t.poketube.fun/",
useragent: config.useragent,
});
module.exports = pokeTubeApiCore;