This commit is contained in:
Theenoro
2026-02-26 09:34:55 +01:00
parent ff9864a1db
commit a6d8f3c2ee
6 changed files with 435 additions and 0 deletions

349
manager.py Normal file
View File

@@ -0,0 +1,349 @@
import importlib
import pkgutil
import re
from engine.plugin import Plugin
from plugins.embed.providers.base import EmbedProvider
class EmbedConsentManager(Plugin):
name = "embed"
priority = 45
def __init__(self, config):
super().__init__(config)
self.providers = []
self.load_providers()
def load_providers(self):
package = "plugins.embed.providers"
pkg = importlib.import_module(package)
for _, name, _ in pkgutil.iter_modules(pkg.__path__):
module = importlib.import_module(f"{package}.{name}")
for attr in dir(module):
cls = getattr(module, attr)
if isinstance(cls, type) and issubclass(cls, EmbedProvider) and cls is not EmbedProvider:
if hasattr(cls, "pattern") and hasattr(cls, "name"):
provider_config = self.config.get(cls.name, {})
self.providers.append(cls(provider_config))
def after_markdown(self, builder, html):
for provider in self.providers:
html = provider.match(html)
return html
return super().after_markdown(builder, html)
def after_page_render(self, builder, page, content):
pattern = r"<\/body>"
content = re.sub(pattern, self.inject_assets() + "BLAAA</body>", content, 0, re.MULTILINE)
return content
def after_build(self, builder):
settings_path = builder.config.output_dir / "privacy-settings.html"
settings_path.write_text(self.generate_settings_page(), encoding="utf-8")
def inject_assets(self):
return self.css() + self.script()
def generate_settings_page(self):
cards = ""
for p in self.providers:
cards += f"""
<div class="provider-card">
<div>
<strong>{p.name.title()}</strong>
<div style="font-size:0.9rem;color:var(--muted);">
External content provider
</div>
</div>
<label class="switch">
<input type="checkbox"
onchange="toggleProvider('{p.name}', this)"
id="toggle-{p.name}">
<span class="slider"></span>
</label>
</div>
"""
return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Privacy Settings</title>
{self.css()}
</head>
<body>
<div class="settings-container">
<h1>Privacy Settings</h1>
<p>Control which external providers may load content on this site.</p>
{cards}
<button class="btn-danger" onclick="revokeAll()">
Revoke All Permissions
</button>
</div>
{self.script()}
<script>
document.addEventListener("DOMContentLoaded", function() {{
{''.join([f"""
if(localStorage.getItem("embed-provider-{p.name}") === "granted") {{
document.getElementById("toggle-{p.name}").checked = true;
}}
""" for p in self.providers])}
}});
</script>
</body>
</html>
"""
def css(self):
return """
<style>
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
/* ===== Embed Card ===== */
.embed-consent {
position: relative;
margin: 30px 0;
border-radius: 18px;
overflow: hidden;
backdrop-filter: blur(20px);
background: var(--card);
border: 1px solid rgba(255,255,255,0.2);
transition: all 0.3s ease;
}
.embed-overlay {
padding: 60px 25px;
text-align: center;
}
.embed-box h3 {
margin-bottom: 10px;
font-size: 1.2rem;
}
.embed-box p {
color: var(--muted);
}
.embed-accept {
margin-top: 20px;
padding: 12px 28px;
border-radius: 10px;
border: none;
background: var(--accent);
color: white;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.embed-accept:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
}
/* ===== Spinner ===== */
.embed-spinner {
width: 50px;
height: 50px;
border-radius: 50%;
border: 5px solid rgba(255,255,255,0.2);
border-top: 5px solid var(--accent);
animation: spin 0.8s linear infinite;
margin: 80px auto;
}
@keyframes spin {
100% { transform: rotate(360deg); }
}
/* ===== Settings Page ===== */
.settings-container {
max-width: 800px;
margin: 80px auto;
padding: 40px;
}
.settings-container h1 {
font-size: 2rem;
margin-bottom: 10px;
}
.settings-container p {
color: var(--muted);
margin-bottom: 40px;
}
/* Provider Card */
.provider-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
margin-bottom: 20px;
border-radius: 16px;
backdrop-filter: blur(20px);
background: var(--card);
border: 1px solid rgba(255,255,255,0.2);
transition: 0.2s ease;
}
.provider-card:hover {
transform: translateY(-3px);
}
/* Toggle Switch */
.switch {
position: relative;
width: 52px;
height: 28px;
}
.switch input {
display: none;
}
.slider {
position: absolute;
cursor: pointer;
background: #ccc;
border-radius: 50px;
inset: 0;
transition: 0.3s;
}
.slider:before {
content: "";
position: absolute;
height: 22px;
width: 22px;
left: 3px;
bottom: 3px;
background: white;
border-radius: 50%;
transition: 0.3s;
}
input:checked + .slider {
background: var(--accent);
}
input:checked + .slider:before {
transform: translateX(24px);
}
/* Buttons */
.btn-danger {
margin-top: 30px;
padding: 12px 25px;
border-radius: 10px;
border: none;
background: var(--danger);
color: white;
cursor: pointer;
font-weight: 600;
}
.btn-danger:hover {
opacity: 0.9;
}
</style>
"""
# ---------------------------------
# JavaScript
# ---------------------------------
def script(self):
return """
<script>
function getState(provider) {
return localStorage.getItem("embed-provider-" + provider);
}
function setState(provider, state) {
localStorage.setItem("embed-provider-" + provider, state);
}
function acceptProvider(provider) {
setState(provider, "granted");
document.querySelectorAll('.embed-consent[data-provider="' + provider + '"]')
.forEach(loadEmbed);
}
function loadEmbed(container) {
const template = container.querySelector(".embed-template");
if (!template) return;
container.innerHTML = '<div class="embed-spinner"></div>';
setTimeout(() => {
const clone = template.content.cloneNode(true);
container.innerHTML = "";
container.appendChild(clone);
container.style.opacity = "0";
setTimeout(()=> container.style.opacity="1",50);
}, 600);
}
document.addEventListener("DOMContentLoaded", function(){
document.querySelectorAll(".embed-consent").forEach(container => {
const provider = container.dataset.provider;
if (getState(provider) === "granted") {
loadEmbed(container);
}
});
});
function toggleProvider(provider, checkbox) {
if (checkbox.checked) {
setState(provider, "granted");
} else {
setState(provider, "denied");
}
}
function revokeAll() {
Object.keys(localStorage).forEach(k=>{
if(k.startsWith("embed-provider-")) localStorage.removeItem(k);
});
alert("All provider consent revoked.");
}
</script>
"""
def revocation_script(self):
return '''
<script>
function revokeProvider(provider){
localStorage.removeItem("embed-consent-" + provider);
alert(provider + " consent revoked. Reloading.");
location.reload();
}
function revokeAll(){
Object.keys(localStorage).forEach(k=>{
if(k.startsWith("embed-consent-")) localStorage.removeItem(k);
});
alert("All embed consent revoked. Reloading.");
location.reload();
}
</script>
'''