From 942b78e38d3be8aa86bcc21126469417bca9fdee Mon Sep 17 00:00:00 2001 From: mx5kevin <47847121+mx5kevin@users.noreply.github.com> Date: Fri, 15 May 2026 04:30:31 +0000 Subject: [PATCH 1/6] Delete plugins/ContentFilter directory Removed ContentFilter plugin. See issues: https://github.com/EpixZone/EpixNet/issues/17 and https://github.com/EpixZone/EpixNet/issues/10 --- plugins/ContentFilter/ContentFilterPlugin.py | 281 ------------------ plugins/ContentFilter/ContentFilterStorage.py | 165 ---------- .../ContentFilter/Test/TestContentFilter.py | 82 ----- plugins/ContentFilter/Test/conftest.py | 1 - plugins/ContentFilter/Test/pytest.ini | 5 - plugins/ContentFilter/__init__.py | 1 - plugins/ContentFilter/languages/hu.json | 6 - plugins/ContentFilter/languages/it.json | 6 - plugins/ContentFilter/languages/jp.json | 6 - plugins/ContentFilter/languages/pt-br.json | 6 - plugins/ContentFilter/languages/zh-tw.json | 6 - plugins/ContentFilter/languages/zh.json | 6 - plugins/ContentFilter/media/blocklisted.html | 89 ------ plugins/ContentFilter/media/js/EpixFrame.js | 119 -------- plugins/ContentFilter/plugin_info.json | 5 - 15 files changed, 784 deletions(-) delete mode 100644 plugins/ContentFilter/ContentFilterPlugin.py delete mode 100644 plugins/ContentFilter/ContentFilterStorage.py delete mode 100644 plugins/ContentFilter/Test/TestContentFilter.py delete mode 100644 plugins/ContentFilter/Test/conftest.py delete mode 100644 plugins/ContentFilter/Test/pytest.ini delete mode 100644 plugins/ContentFilter/__init__.py delete mode 100644 plugins/ContentFilter/languages/hu.json delete mode 100644 plugins/ContentFilter/languages/it.json delete mode 100644 plugins/ContentFilter/languages/jp.json delete mode 100644 plugins/ContentFilter/languages/pt-br.json delete mode 100644 plugins/ContentFilter/languages/zh-tw.json delete mode 100644 plugins/ContentFilter/languages/zh.json delete mode 100644 plugins/ContentFilter/media/blocklisted.html delete mode 100644 plugins/ContentFilter/media/js/EpixFrame.js delete mode 100644 plugins/ContentFilter/plugin_info.json diff --git a/plugins/ContentFilter/ContentFilterPlugin.py b/plugins/ContentFilter/ContentFilterPlugin.py deleted file mode 100644 index 29605414e..000000000 --- a/plugins/ContentFilter/ContentFilterPlugin.py +++ /dev/null @@ -1,281 +0,0 @@ -import time -import re -import html -import os - -from Plugin import PluginManager -from Translate import Translate -from Config import config -from util.Flag import flag - -from .ContentFilterStorage import ContentFilterStorage - - -plugin_dir = os.path.dirname(__file__) - -if "_" not in locals(): - _ = Translate(plugin_dir + "/languages/") - - -@PluginManager.registerTo("SiteManager") -class SiteManagerPlugin(object): - def load(self, *args, **kwargs): - global filter_storage - super(SiteManagerPlugin, self).load(*args, **kwargs) - filter_storage = ContentFilterStorage(site_manager=self) - - def add(self, address, *args, **kwargs): - should_ignore_block = kwargs.get("ignore_block") or kwargs.get("settings") - if should_ignore_block: - block_details = None - elif filter_storage.isSiteblocked(address): - block_details = filter_storage.getSiteblockDetails(address) - else: - address_hashed = filter_storage.getSiteAddressHashed(address) - if filter_storage.isSiteblocked(address_hashed): - block_details = filter_storage.getSiteblockDetails(address_hashed) - else: - block_details = None - - if block_details: - raise Exception(f'Site blocked: {html.escape(block_details.get("reason", "unknown reason"))}') - else: - return super(SiteManagerPlugin, self).add(address, *args, **kwargs) - - -@PluginManager.registerTo("UiWebsocket") -class UiWebsocketPlugin(object): - # Mute - def cbMuteAdd(self, to, auth_address, cert_user_id, reason): - filter_storage.file_content["mutes"][auth_address] = { - "cert_user_id": cert_user_id, "reason": reason, "source": self.site.address, "date_added": time.time() - } - filter_storage.save() - filter_storage.changeDbs(auth_address, "remove") - self.response(to, "ok") - - @flag.no_multiuser - def actionMuteAdd(self, to, auth_address, cert_user_id, reason): - self.cmd( - "prompt", - [_["Remove all content from %s?"] % html.escape(cert_user_id), reason, _["Mute"]], - lambda res: self.cbMuteAdd(to, auth_address, cert_user_id, res if res else reason) - ) - - @flag.no_multiuser - def cbMuteRemove(self, to, auth_address): - del filter_storage.file_content["mutes"][auth_address] - filter_storage.save() - filter_storage.changeDbs(auth_address, "load") - self.response(to, "ok") - - @flag.no_multiuser - def actionMuteRemove(self, to, auth_address): - if "ADMIN" in self.getPermissions(to): - self.cbMuteRemove(to, auth_address) - else: - cert_user_id = html.escape(filter_storage.file_content["mutes"][auth_address]["cert_user_id"]) - self.cmd( - "confirm", - [_["Unmute %s?"] % cert_user_id, _["Unmute"]], - lambda res: self.cbMuteRemove(to, auth_address) - ) - - @flag.admin - def actionMuteList(self, to): - self.response(to, filter_storage.file_content["mutes"]) - - # Siteblock - @flag.no_multiuser - @flag.admin - def actionSiteblockIgnoreAddSite(self, to, site_address): - if site_address in filter_storage.site_manager.sites: - return {"error": "Site already added"} - else: - if filter_storage.site_manager.need(site_address, ignore_block=True): - return "ok" - else: - return {"error": "Invalid address"} - - @flag.no_multiuser - @flag.admin - def actionSiteblockAdd(self, to, site_address, reason=None): - filter_storage.file_content["siteblocks"][site_address] = {"date_added": time.time(), "reason": reason} - filter_storage.save() - self.response(to, "ok") - - @flag.no_multiuser - @flag.admin - def actionSiteblockRemove(self, to, site_address): - del filter_storage.file_content["siteblocks"][site_address] - filter_storage.save() - self.response(to, "ok") - - @flag.admin - def actionSiteblockList(self, to): - self.response(to, filter_storage.file_content["siteblocks"]) - - @flag.admin - def actionSiteblockGet(self, to, site_address): - if filter_storage.isSiteblocked(site_address): - res = filter_storage.getSiteblockDetails(site_address) - else: - site_address_hashed = filter_storage.getSiteAddressHashed(site_address) - if filter_storage.isSiteblocked(site_address_hashed): - res = filter_storage.getSiteblockDetails(site_address_hashed) - else: - res = {"error": "Site block not found"} - self.response(to, res) - - # Include - @flag.no_multiuser - def actionFilterIncludeAdd(self, to, inner_path, description=None, address=None): - if address: - if "ADMIN" not in self.getPermissions(to): - return self.response(to, {"error": "Forbidden: Only ADMIN sites can manage different site include"}) - site = self.server.sites[address] - else: - address = self.site.address - site = self.site - - if "ADMIN" in self.getPermissions(to): - self.cbFilterIncludeAdd(to, True, address, inner_path, description) - else: - content = site.storage.loadJson(inner_path) - title = _["New shared global content filter: %s (%s sites, %s users)"] % ( - html.escape(inner_path), len(content.get("siteblocks", {})), len(content.get("mutes", {})) - ) - - self.cmd( - "confirm", - [title, "Add"], - lambda res: self.cbFilterIncludeAdd(to, res, address, inner_path, description) - ) - - def cbFilterIncludeAdd(self, to, res, address, inner_path, description): - if not res: - self.response(to, res) - return False - - filter_storage.includeAdd(address, inner_path, description) - self.response(to, "ok") - - @flag.no_multiuser - def actionFilterIncludeRemove(self, to, inner_path, address=None): - if address: - if "ADMIN" not in self.getPermissions(to): - return self.response(to, {"error": "Forbidden: Only ADMIN sites can manage different site include"}) - else: - address = self.site.address - - key = "%s/%s" % (address, inner_path) - if key not in filter_storage.file_content["includes"]: - self.response(to, {"error": "Include not found"}) - filter_storage.includeRemove(address, inner_path) - self.response(to, "ok") - - def actionFilterIncludeList(self, to, all_sites=False, filters=False): - if all_sites and "ADMIN" not in self.getPermissions(to): - return self.response(to, {"error": "Forbidden: Only ADMIN sites can list all sites includes"}) - - back = [] - includes = filter_storage.file_content.get("includes", {}).values() - for include in includes: - if not all_sites and include["address"] != self.site.address: - continue - if filters: - include = dict(include) # Don't modify original file_content - include_site = filter_storage.site_manager.get(include["address"]) - if not include_site: - continue - content = include_site.storage.loadJson(include["inner_path"]) - include["mutes"] = content.get("mutes", {}) - include["siteblocks"] = content.get("siteblocks", {}) - back.append(include) - self.response(to, back) - - -@PluginManager.registerTo("SiteStorage") -class SiteStoragePlugin(object): - def updateDbFile(self, inner_path, file=None, cur=None): - if file is not False: # File deletion always allowed - # Find user directory names (epix1... addresses or xID names) in file path - matches = re.findall("/([A-Za-z0-9][A-Za-z0-9.]{2,})/", str(inner_path)) - # Check if any of the adresses are in the mute list - for auth_address in matches: - if filter_storage.isMuted(auth_address): - self.log.debug(f'Mute match: {auth_address}, ignoring {inner_path}') - return False - - return super(SiteStoragePlugin, self).updateDbFile(inner_path, file=file, cur=cur) - - def onUpdated(self, inner_path, file=None): - file_path = f'{self.site.address}/{inner_path}' - if file_path in filter_storage.file_content['includes']: - self.log.debug('Filter file updated: {inner_path}') - filter_storage.includeUpdateAll() - return super(SiteStoragePlugin, self).onUpdated(inner_path, file=file) - -@PluginManager.registerTo("Site") -class SitePlugin(object): - def needFile(self, inner_path, update=False, blocking=True, peer=None, priority=0): - self.log.debug(f'needFile {inner_path}') - matches = re.findall('/([A-Za-z0-9][A-Za-z0-9.]{2,})/', str(inner_path)) - for auth_address in matches: - if filter_storage.isMuted(auth_address): - self.log.info(f'Mute match in Site.needFile: {auth_address}, ignoring {inner_path}') - return False - return super(SitePlugin, self).needFile(inner_path, update, blocking, peer, priority) - -@PluginManager.registerTo("FileRequest") -class FileRequestPlugin: - def actionUpdate(self, params): - inner_path = params.get('inner_path', '') - matches = re.findall('/([A-Za-z0-9][A-Za-z0-9.]{2,})/', str(inner_path)) - for auth_address in matches: - if filter_storage.isMuted(auth_address): - self.log.info(f'Mute match in FileRequest.actionUpdate: {auth_address}, ignoring {inner_path}') - self.response({'ok': f'Thanks, file {inner_path} updated!'}) - return False - return super(FileRequestPlugin, self).actionUpdate(params) - -@PluginManager.registerTo("UiRequest") -class UiRequestPlugin(object): - def actionWrapper(self, path, extra_headers=None): - match = re.match(r"/(?P
[A-Za-z0-9\._-]+)(?P/.*|$)", path) - if not match: - return False - address = match.group("address") - - if self.server.site_manager.get(address): # Site already exists - return super(UiRequestPlugin, self).actionWrapper(path, extra_headers) - - if self.isDomain(address): - address = self.resolveDomain(address) - - if address: - address_hashed = filter_storage.getSiteAddressHashed(address) - else: - address_hashed = None - - if filter_storage.isSiteblocked(address) or filter_storage.isSiteblocked(address_hashed): - site = self.server.site_manager.get(config.homepage) - if not extra_headers: - extra_headers = {} - - script_nonce = self.getScriptNonce() - - self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce) - return iter([super(UiRequestPlugin, self).renderWrapper( - site, path, "uimedia/plugins/contentfilter/blocklisted.html?address=" + address, - "Blacklisted site", extra_headers, show_loadingscreen=False, script_nonce=script_nonce - )]) - else: - return super(UiRequestPlugin, self).actionWrapper(path, extra_headers) - - def actionUiMedia(self, path, *args, **kwargs): - if path.startswith("/uimedia/plugins/contentfilter/"): - file_path = path.replace("/uimedia/plugins/contentfilter/", plugin_dir + "/media/") - return self.actionFile(file_path) - else: - return super(UiRequestPlugin, self).actionUiMedia(path) diff --git a/plugins/ContentFilter/ContentFilterStorage.py b/plugins/ContentFilter/ContentFilterStorage.py deleted file mode 100644 index 1074aee04..000000000 --- a/plugins/ContentFilter/ContentFilterStorage.py +++ /dev/null @@ -1,165 +0,0 @@ -import os -import json -import logging -import collections -import time -import hashlib -from pathlib import Path - -from Debug import Debug -from Plugin import PluginManager -from Config import config -from util import helper - - -class ContentFilterStorage(object): - def __init__(self, site_manager): - self.log = logging.getLogger("ContentFilterStorage") - self.file_path = config.config_dir / 'filters.json' - self.site_manager = site_manager - self.file_content = self.load() - - # Set default values for filters.json - if not self.file_content: - self.file_content = {} - - # Site blacklist renamed to site blocks - if "site_blacklist" in self.file_content: - self.file_content["siteblocks"] = self.file_content["site_blacklist"] - del self.file_content["site_blacklist"] - - for key in ["mutes", "siteblocks", "includes"]: - if key not in self.file_content: - self.file_content[key] = {} - - self.include_filters = collections.defaultdict(set) # Merged list of mutes and blacklists from all include - self.includeUpdateAll(update_site_dbs=False) - - def load(self): - # Rename previously used mutes.json -> filters.json - if (config.config_dir / 'mutes.json').is_file(): - self.log.info("Renaming mutes.json to filters.json...") - os.rename(config.config_dir / 'mutes.json', self.file_path) - if self.file_path.is_file(): - try: - return json.load(self.file_path.open()) - except Exception as err: - self.log.error("Error loading filters.json: %s" % err) - return None - else: - return None - - def includeUpdateAll(self, update_site_dbs=True): - s = time.time() - new_include_filters = collections.defaultdict(set) - - # Load all include files data into a merged set - for include_path in self.file_content["includes"]: - address, inner_path = include_path.split("/", 1) - try: - content = self.site_manager.get(address).storage.loadJson(inner_path) - except Exception as err: - self.log.warning( - "Error loading include %s: %s" % - (include_path, Debug.formatException(err)) - ) - continue - - for key, val in content.items(): - if type(val) is not dict: - continue - - new_include_filters[key].update(val.keys()) - - mutes_added = new_include_filters["mutes"].difference(self.include_filters["mutes"]) - mutes_removed = self.include_filters["mutes"].difference(new_include_filters["mutes"]) - - self.include_filters = new_include_filters - - if update_site_dbs: - for auth_address in mutes_added: - self.changeDbs(auth_address, "remove") - - for auth_address in mutes_removed: - if not self.isMuted(auth_address): - self.changeDbs(auth_address, "load") - - num_mutes = len(self.include_filters["mutes"]) - num_siteblocks = len(self.include_filters["siteblocks"]) - self.log.debug( - "Loaded %s mutes, %s blocked sites from %s includes in %.3fs" % - (num_mutes, num_siteblocks, len(self.file_content["includes"]), time.time() - s) - ) - - def includeAdd(self, address, inner_path, description=None): - self.file_content["includes"]["%s/%s" % (address, inner_path)] = { - "date_added": time.time(), - "address": address, - "description": description, - "inner_path": inner_path - } - self.includeUpdateAll() - self.save() - - def includeRemove(self, address, inner_path): - del self.file_content["includes"]["%s/%s" % (address, inner_path)] - self.includeUpdateAll() - self.save() - - def save(self): - s = time.time() - helper.atomicWrite(self.file_path, json.dumps(self.file_content, indent=2, sort_keys=True).encode("utf8")) - self.log.debug("Saved in %.3fs" % (time.time() - s)) - - def isMuted(self, auth_address): - if auth_address in self.file_content["mutes"] or auth_address in self.include_filters["mutes"]: - return True - else: - return False - - def getSiteAddressHashed(self, address): - return "0x" + hashlib.sha256(address.encode("ascii")).hexdigest() - - def isSiteblocked(self, address): - if address in self.file_content["siteblocks"] or address in self.include_filters["siteblocks"]: - return True - return False - - def getSiteblockDetails(self, address): - details = self.file_content["siteblocks"].get(address) - if not details: - address_sha256 = self.getSiteAddressHashed(address) - details = self.file_content["siteblocks"].get(address_sha256) - - if not details: - includes = self.file_content.get("includes", {}).values() - for include in includes: - include_site = self.site_manager.get(include["address"]) - if not include_site: - continue - content = include_site.storage.loadJson(include["inner_path"]) - details = content.get("siteblocks", {}).get(address) - if details: - details["include"] = include - break - - return details - - # Search and remove or readd files of an user - def changeDbs(self, auth_address, action): - self.log.debug("Mute action %s on user %s" % (action, auth_address)) - res = list(self.site_manager.list().values())[0].content_manager.contents.db.execute( - "SELECT * FROM content LEFT JOIN site USING (site_id) WHERE inner_path LIKE :inner_path", - {"inner_path": "%%/%s/%%" % auth_address} - ) - for row in res: - site = self.site_manager.sites.get(row["address"]) - if not site: - continue - dir_inner_path = helper.getDirname(row["inner_path"]) - for file_name in site.storage.walk(Path(dir_inner_path)): - if action == "remove": - site.storage.delete(dir_inner_path + file_name) - else: - site.storage.onUpdated(dir_inner_path + file_name) - site.onFileDone(dir_inner_path + file_name) diff --git a/plugins/ContentFilter/Test/TestContentFilter.py b/plugins/ContentFilter/Test/TestContentFilter.py deleted file mode 100644 index e1b37b163..000000000 --- a/plugins/ContentFilter/Test/TestContentFilter.py +++ /dev/null @@ -1,82 +0,0 @@ -import pytest -from ContentFilter import ContentFilterPlugin -from Site import SiteManager - - -@pytest.fixture -def filter_storage(): - ContentFilterPlugin.filter_storage = ContentFilterPlugin.ContentFilterStorage(SiteManager.site_manager) - return ContentFilterPlugin.filter_storage - - -@pytest.mark.usefixtures("resetSettings") -@pytest.mark.usefixtures("resetTempSettings") -class TestContentFilter: - def createInclude(self, site): - site.storage.writeJson("filters.json", { - "mutes": {"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C": {}}, - "siteblocks": {site.address: {}} - }) - - def testIncludeLoad(self, site, filter_storage): - self.createInclude(site) - filter_storage.file_content["includes"]["%s/%s" % (site.address, "filters.json")] = { - "date_added": 1528295893, - } - - assert not filter_storage.include_filters["mutes"] - assert not filter_storage.isMuted("1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C") - assert not filter_storage.isSiteblocked(site.address) - filter_storage.includeUpdateAll(update_site_dbs=False) - assert len(filter_storage.include_filters["mutes"]) == 1 - assert filter_storage.isMuted("1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C") - assert filter_storage.isSiteblocked(site.address) - - def testIncludeAdd(self, site, filter_storage): - self.createInclude(site) - query_num_json = "SELECT COUNT(*) AS num FROM json WHERE directory = 'users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C'" - assert not filter_storage.isSiteblocked(site.address) - assert not filter_storage.isMuted("1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C") - assert site.storage.query(query_num_json).fetchone()["num"] == 2 - - # Add include - filter_storage.includeAdd(site.address, "filters.json") - - assert filter_storage.isSiteblocked(site.address) - assert filter_storage.isMuted("1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C") - assert site.storage.query(query_num_json).fetchone()["num"] == 0 - - # Remove include - filter_storage.includeRemove(site.address, "filters.json") - - assert not filter_storage.isSiteblocked(site.address) - assert not filter_storage.isMuted("1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C") - assert site.storage.query(query_num_json).fetchone()["num"] == 2 - - def testIncludeChange(self, site, filter_storage): - self.createInclude(site) - filter_storage.includeAdd(site.address, "filters.json") - assert filter_storage.isSiteblocked(site.address) - assert filter_storage.isMuted("1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C") - - # Add new blocked site - assert not filter_storage.isSiteblocked("1Hello") - - filter_content = site.storage.loadJson("filters.json") - filter_content["siteblocks"]["1Hello"] = {} - site.storage.writeJson("filters.json", filter_content) - - assert filter_storage.isSiteblocked("1Hello") - - # Add new muted user - query_num_json = "SELECT COUNT(*) AS num FROM json WHERE directory = 'users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q'" - assert not filter_storage.isMuted("1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q") - assert site.storage.query(query_num_json).fetchone()["num"] == 2 - - filter_content["mutes"]["1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q"] = {} - site.storage.writeJson("filters.json", filter_content) - - assert filter_storage.isMuted("1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q") - assert site.storage.query(query_num_json).fetchone()["num"] == 0 - - diff --git a/plugins/ContentFilter/Test/conftest.py b/plugins/ContentFilter/Test/conftest.py deleted file mode 100644 index 634e66e2e..000000000 --- a/plugins/ContentFilter/Test/conftest.py +++ /dev/null @@ -1 +0,0 @@ -from src.Test.conftest import * diff --git a/plugins/ContentFilter/Test/pytest.ini b/plugins/ContentFilter/Test/pytest.ini deleted file mode 100644 index d09210d1d..000000000 --- a/plugins/ContentFilter/Test/pytest.ini +++ /dev/null @@ -1,5 +0,0 @@ -[pytest] -python_files = Test*.py -addopts = -rsxX -v --durations=6 -markers = - webtest: mark a test as a webtest. \ No newline at end of file diff --git a/plugins/ContentFilter/__init__.py b/plugins/ContentFilter/__init__.py deleted file mode 100644 index 2cbca8eea..000000000 --- a/plugins/ContentFilter/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import ContentFilterPlugin diff --git a/plugins/ContentFilter/languages/hu.json b/plugins/ContentFilter/languages/hu.json deleted file mode 100644 index 9b57e6979..000000000 --- a/plugins/ContentFilter/languages/hu.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Hide all content from %s?": "%s tartalmaniak elrejtése?", - "Mute": "Elnémítás", - "Unmute %s?": "%s tartalmaniak megjelenítése?", - "Unmute": "Némítás visszavonása" -} diff --git a/plugins/ContentFilter/languages/it.json b/plugins/ContentFilter/languages/it.json deleted file mode 100644 index 9a2c6761d..000000000 --- a/plugins/ContentFilter/languages/it.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Hide all content from %s?": "%s Vuoi nascondere i contenuti di questo utente ?", - "Mute": "Attiva Silenzia", - "Unmute %s?": "%s Vuoi mostrare i contenuti di questo utente ?", - "Unmute": "Disattiva Silenzia" -} diff --git a/plugins/ContentFilter/languages/jp.json b/plugins/ContentFilter/languages/jp.json deleted file mode 100644 index ef586a1a6..000000000 --- a/plugins/ContentFilter/languages/jp.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Hide all content from %s?": "%s のコンテンツをすべて隠しますか?", - "Mute": "ミュート", - "Unmute %s?": "%s のミュートを解除しますか?", - "Unmute": "ミュート解除" -} diff --git a/plugins/ContentFilter/languages/pt-br.json b/plugins/ContentFilter/languages/pt-br.json deleted file mode 100644 index 3c6bfbdcf..000000000 --- a/plugins/ContentFilter/languages/pt-br.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Hide all content from %s?": "%s Ocultar todo o conteúdo de ?", - "Mute": "Ativar o Silêncio", - "Unmute %s?": "%s Você quer mostrar o conteúdo deste usuário ?", - "Unmute": "Desligar o silêncio" -} diff --git a/plugins/ContentFilter/languages/zh-tw.json b/plugins/ContentFilter/languages/zh-tw.json deleted file mode 100644 index 0995f3a0f..000000000 --- a/plugins/ContentFilter/languages/zh-tw.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Hide all content from %s?": "屏蔽 %s 的所有內容?", - "Mute": "屏蔽", - "Unmute %s?": "對 %s 解除屏蔽?", - "Unmute": "解除屏蔽" -} diff --git a/plugins/ContentFilter/languages/zh.json b/plugins/ContentFilter/languages/zh.json deleted file mode 100644 index bf63f1075..000000000 --- a/plugins/ContentFilter/languages/zh.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Hide all content from %s?": "屏蔽 %s 的所有内容?", - "Mute": "屏蔽", - "Unmute %s?": "对 %s 解除屏蔽?", - "Unmute": "解除屏蔽" -} diff --git a/plugins/ContentFilter/media/blocklisted.html b/plugins/ContentFilter/media/blocklisted.html deleted file mode 100644 index 156dd127e..000000000 --- a/plugins/ContentFilter/media/blocklisted.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - -
-

Site blocked

-

This site is on your blocklist:

-
-
Too much image
-
on 2015-01-25 12:32:11
-
- -
- - - - - - diff --git a/plugins/ContentFilter/media/js/EpixFrame.js b/plugins/ContentFilter/media/js/EpixFrame.js deleted file mode 100644 index 010414c06..000000000 --- a/plugins/ContentFilter/media/js/EpixFrame.js +++ /dev/null @@ -1,119 +0,0 @@ -// Version 1.0.0 - Initial release -// Version 1.1.0 (2017-08-02) - Added cmdp function that returns promise instead of using callback -// Version 1.2.0 (2017-08-02) - Added Ajax monkey patch to emulate XMLHttpRequest over EpixFrame API - -const CMD_INNER_READY = 'innerReady' -const CMD_RESPONSE = 'response' -const CMD_WRAPPER_READY = 'wrapperReady' -const CMD_PING = 'ping' -const CMD_PONG = 'pong' -const CMD_WRAPPER_OPENED_WEBSOCKET = 'wrapperOpenedWebsocket' -const CMD_WRAPPER_CLOSE_WEBSOCKET = 'wrapperClosedWebsocket' - -class EpixFrame { - constructor(url) { - this.url = url - this.waiting_cb = {} - this.wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1") - this.connect() - this.next_message_id = 1 - this.init() - } - - init() { - return this - } - - connect() { - this.target = window.parent - window.addEventListener('message', e => this.onMessage(e), false) - this.cmd(CMD_INNER_READY) - } - - onMessage(e) { - let message = e.data - let cmd = message.cmd - if (cmd === CMD_RESPONSE) { - if (this.waiting_cb[message.to] !== undefined) { - this.waiting_cb[message.to](message.result) - } - else { - this.log("Websocket callback not found:", message) - } - } else if (cmd === CMD_WRAPPER_READY) { - this.cmd(CMD_INNER_READY) - } else if (cmd === CMD_PING) { - this.response(message.id, CMD_PONG) - } else if (cmd === CMD_WRAPPER_OPENED_WEBSOCKET) { - this.onOpenWebsocket() - } else if (cmd === CMD_WRAPPER_CLOSE_WEBSOCKET) { - this.onCloseWebsocket() - } else { - this.onRequest(cmd, message) - } - } - - onRequest(cmd, message) { - this.log("Unknown request", message) - } - - response(to, result) { - this.send({ - cmd: CMD_RESPONSE, - to: to, - result: result - }) - } - - cmd(cmd, params={}, cb=null) { - this.send({ - cmd: cmd, - params: params - }, cb) - } - - cmdp(cmd, params={}) { - return new Promise((resolve, reject) => { - this.cmd(cmd, params, (res) => { - if (res && res.error) { - reject(res.error) - } else { - resolve(res) - } - }) - }) - } - - send(message, cb=null) { - message.wrapper_nonce = this.wrapper_nonce - message.id = this.next_message_id - this.next_message_id++ - this.target.postMessage(message, '*') - if (cb) { - this.waiting_cb[message.id] = cb - } - } - - log(...args) { - console.log.apply(console, ['[EpixFrame]'].concat(args)) - } - - onOpenWebsocket() { - this.log('Websocket open') - } - - onCloseWebsocket() { - this.log('Websocket close') - } - - monkeyPatchAjax() { - var page = this - XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open - this.cmd("wrapperGetAjaxKey", [], (res) => { this.ajax_key = res }) - var newOpen = function (method, url, async) { - url += "?ajax_key=" + page.ajax_key - return this.realOpen(method, url, async) - } - XMLHttpRequest.prototype.open = newOpen - } -} diff --git a/plugins/ContentFilter/plugin_info.json b/plugins/ContentFilter/plugin_info.json deleted file mode 100644 index f63bc984c..000000000 --- a/plugins/ContentFilter/plugin_info.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "ContentFilter", - "description": "Manage site and user block list.", - "default": "enabled" -} \ No newline at end of file From f511f728c6d96087de6750332c9a3020fdd538e8 Mon Sep 17 00:00:00 2001 From: mx5kevin <47847121+mx5kevin@users.noreply.github.com> Date: Fri, 15 May 2026 09:24:49 +0000 Subject: [PATCH 2/6] Allow proxy users to add new sites Allow proxy users to add new sites, to make the entire network accessible through a proxy. The NoNew sites if it is on, turn this feature off anyway. --- plugins/disabled-Multiuser/MultiuserPlugin.py | 556 +++++++++--------- 1 file changed, 278 insertions(+), 278 deletions(-) diff --git a/plugins/disabled-Multiuser/MultiuserPlugin.py b/plugins/disabled-Multiuser/MultiuserPlugin.py index 18bdcf205..6a59dd47f 100644 --- a/plugins/disabled-Multiuser/MultiuserPlugin.py +++ b/plugins/disabled-Multiuser/MultiuserPlugin.py @@ -1,278 +1,278 @@ -import re -import sys -import json - -from Config import config -from Plugin import PluginManager -from Crypt import CryptEpix -from . import UserPlugin -from util.Flag import flag -from Translate import translate as _ - -# We can only import plugin host clases after the plugins are loaded -@PluginManager.afterLoad -def importPluginnedClasses(): - global UserManager - from User import UserManager - -try: - local_master_addresses = set(json.load((config.private_dir / 'users.json').open()).keys()) # Users in users.json -except Exception as err: - local_master_addresses = set() - - -@PluginManager.registerTo("UiRequest") -class UiRequestPlugin(object): - def __init__(self, *args, **kwargs): - self.user_manager = UserManager.user_manager - super(UiRequestPlugin, self).__init__(*args, **kwargs) - - def parsePath(self, path): - return super(UiRequestPlugin, self).parsePath(path) - - # Create new user and inject user welcome message if necessary - # Return: Html body also containing the injection - def actionWrapper(self, path, extra_headers=None): - - match = re.match("/(?P
[A-Za-z0-9\._-]+)(?P/.*|$)", path) - if not match: - return False - - inner_path = match.group("inner_path").lstrip("/") - html_request = "." not in inner_path or inner_path.endswith(".html") # Only inject html to html requests - - user_created = False - if html_request: - user = self.getCurrentUser() # Get user from cookie - if not user: # No user found by cookie - user = self.user_manager.create() - user_created = True - else: - user = None - - # Disable new site creation if --multiuser_no_new_sites enabled - if config.multiuser_no_new_sites: - path_parts = self.parsePath(path) - if not self.server.site_manager.get(match.group("address")) and (not user or user.master_address not in local_master_addresses): - self.sendHeader(404) - return self.formatError("Not Found", "Adding new sites disabled on this proxy", details=False) - - if user_created: - if not extra_headers: - extra_headers = {} - extra_headers['Set-Cookie'] = "master_address=%s;path=/;max-age=2592000;" % user.master_address # = 30 days - - loggedin = self.get.get("login") == "done" - - back_generator = super(UiRequestPlugin, self).actionWrapper(path, extra_headers) # Get the wrapper frame output - - if not back_generator: # Wrapper error or not string returned, injection not possible - return False - - elif loggedin: - back = next(back_generator) - inject_html = """ - - - - - """.replace("\t", "") - if user.master_address in local_master_addresses: - message = "Hello master!" - else: - message = "Hello again!" - inject_html = inject_html.replace("{message}", message) - inject_html = inject_html.replace("{script_nonce}", self.getScriptNonce()) - return iter([re.sub(b"\s*\s*$", inject_html.encode(), back)]) # Replace the tags with the injection - - else: # No injection necessary - return back_generator - - # Get the current user based on request's cookies - # Return: User object or None if no match - def getCurrentUser(self): - cookies = self.getCookies() - user = None - if "master_address" in cookies: - users = self.user_manager.list() - user = users.get(cookies["master_address"]) - return user - - -@PluginManager.registerTo("UiWebsocket") -class UiWebsocketPlugin(object): - def __init__(self, *args, **kwargs): - if config.multiuser_no_new_sites: - flag.no_multiuser(self.actionMergerSiteAdd) - - super(UiWebsocketPlugin, self).__init__(*args, **kwargs) - - # Let the page know we running in multiuser mode - def formatServerInfo(self): - server_info = super(UiWebsocketPlugin, self).formatServerInfo() - server_info["multiuser"] = True - if "ADMIN" in self.site.settings["permissions"]: - server_info["master_address"] = self.user.master_address - is_multiuser_admin = config.multiuser_local or self.user.master_address in local_master_addresses - server_info["multiuser_admin"] = is_multiuser_admin - return server_info - - # Show current user's master seed - @flag.admin - def actionUserShowMasterSeed(self, to): - message = "Your unique private key:" - message += "
%s
" % self.user.master_seed - message += "(Save it, you can access your account using this information)" - self.cmd("notification", ["info", message]) - - # Logout user - @flag.admin - def actionUserLogout(self, to): - message = "You have been logged out. Login to another account" - self.cmd("notification", ["done", message, 1000000]) # 1000000 = Show ~forever :) - - script = "document.cookie = 'master_address=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';" - script += "$('#button_notification').on('click', function() { epixframe.cmd(\"userLoginForm\", []); });" - self.cmd("injectScript", script) - # Delete from user_manager - user_manager = UserManager.user_manager - if self.user.master_address in user_manager.users: - if not config.multiuser_local: - del user_manager.users[self.user.master_address] - self.response(to, "Successful logout") - else: - self.response(to, "User not found") - - @flag.admin - def actionUserSet(self, to, master_address): - user_manager = UserManager.user_manager - user = user_manager.get(master_address) - if not user: - raise Exception("No user found") - - script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % master_address - script += "epixframe.cmd('wrapperReload', ['login=done']);" - self.cmd("notification", ["done", "Successful login, reloading page..."]) - self.cmd("injectScript", script) - - self.response(to, "ok") - - @flag.admin - def actionUserSelectForm(self, to): - if not config.multiuser_local: - raise Exception("Only allowed in multiuser local mode") - user_manager = UserManager.user_manager - body = "" + "Change account:" + "" - for master_address, user in user_manager.list().items(): - is_active = self.user.master_address == master_address - if user.certs: - first_cert = next(iter(user.certs.keys())) - title = "%s@%s" % (user.certs[first_cert]["auth_user_name"], first_cert) - else: - title = user.master_address - if len(user.sites) < 2 and not is_active: # Avoid listing ad-hoc created users - continue - if is_active: - css_class = "active" - else: - css_class = "noclass" - body += "%s" % (css_class, user.master_address, title) - - script = """ - $(".notification .select.user").on("click", function() { - $(".notification .select").removeClass('active') - epixframe.response(%s, this.title) - return false - }) - """ % self.next_message_id - - self.cmd("notification", ["ask", body], lambda master_address: self.actionUserSet(to, master_address)) - self.cmd("injectScript", script) - - # Show login form - def actionUserLoginForm(self, to): - self.cmd("prompt", ["Login
Your private key:", "password", "Login"], self.responseUserLogin) - - # Login form submit - def responseUserLogin(self, master_seed): - user_manager = UserManager.user_manager - user = user_manager.get(CryptEpix.privatekeyToAddress(master_seed)) - if not user: - user = user_manager.create(master_seed=master_seed) - if user.master_address: - script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % user.master_address - script += "epixframe.cmd('wrapperReload', ['login=done']);" - self.cmd("notification", ["done", "Successful login, reloading page..."]) - self.cmd("injectScript", script) - else: - self.cmd("notification", ["error", "Error: Invalid master seed"]) - self.actionUserLoginForm(0) - - def hasCmdPermission(self, cmd): - flags = flag.db.get(self.getCmdFuncName(cmd), ()) - is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses - if is_public_proxy_user and "no_multiuser" in flags: - self.cmd("notification", ["info", _("This function ({cmd}) is disabled on this proxy!")]) - return False - else: - return super(UiWebsocketPlugin, self).hasCmdPermission(cmd) - - def actionCertAdd(self, *args, **kwargs): - super(UiWebsocketPlugin, self).actionCertAdd(*args, **kwargs) - master_seed = self.user.master_seed - message = """ - - Hello, welcome to ZeroProxy!
A new, unique account created for you:
- -
- or Download backup as text file -
-
- This is your private key, save it, so you can login next time.
- Warning: Without this key, your account will be lost forever! -

- Ok, Saved it!

- This site allows you to browse EpixNet content, but if you want to secure your account
- and help to keep the network alive, then please run your own EpixNet client.
- """ - - self.cmd("notification", ["info", message]) - - script = """ - $("#button_notification_masterseed").on("click", function() { - this.value = "{master_seed}"; this.setSelectionRange(0,100); - }) - $("#button_notification_download").on("mousedown", function() { - this.href = window.URL.createObjectURL(new Blob(["EpixNet user master seed:\\r\\n{master_seed}"])) - }) - """.replace("{master_seed}", master_seed) - self.cmd("injectScript", script) - - def actionPermissionAdd(self, to, permission): - is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses - if permission == "NOSANDBOX" and is_public_proxy_user: - self.cmd("notification", ["info", "You can't disable sandbox on this proxy!"]) - self.response(to, {"error": "Denied by proxy"}) - return False - else: - return super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission) - - -@PluginManager.registerTo("ConfigPlugin") -class ConfigPlugin(object): - def createArguments(self): - group = self.parser.add_argument_group("Multiuser plugin") - group.add_argument('--multiuser-local', help="Enable unsafe Ui functions and write users to disk", action='store_true') - group.add_argument('--multiuser-no-new-sites', help="Denies adding new sites by normal users", action='store_true') - - return super(ConfigPlugin, self).createArguments() +import re +import sys +import json + +from Config import config +from Plugin import PluginManager +from Crypt import CryptEpix +from . import UserPlugin +from util.Flag import flag +from Translate import translate as _ + +# We can only import plugin host clases after the plugins are loaded +@PluginManager.afterLoad +def importPluginnedClasses(): + global UserManager + from User import UserManager + +try: + local_master_addresses = set(json.load((config.private_dir / 'users.json').open()).keys()) # Users in users.json +except Exception as err: + local_master_addresses = set() + + +@PluginManager.registerTo("UiRequest") +class UiRequestPlugin(object): + def __init__(self, *args, **kwargs): + self.user_manager = UserManager.user_manager + super(UiRequestPlugin, self).__init__(*args, **kwargs) + + def parsePath(self, path): + return super(UiRequestPlugin, self).parsePath(path) + + # Create new user and inject user welcome message if necessary + # Return: Html body also containing the injection + def actionWrapper(self, path, extra_headers=None): + + match = re.match("/(?P
[A-Za-z0-9\._-]+)(?P/.*|$)", path) + if not match: + return False + + inner_path = match.group("inner_path").lstrip("/") + html_request = "." not in inner_path or inner_path.endswith(".html") # Only inject html to html requests + + user_created = False + if html_request: + user = self.getCurrentUser() # Get user from cookie + if not user: # No user found by cookie + user = self.user_manager.create() + user_created = True + else: + user = None + + # Disable new site creation if --multiuser_no_new_sites enabled "Adding new sites disabled on this proxy", details=True or False turn it False if You want block users to add new websites to your server! + if config.multiuser_no_new_sites: + path_parts = self.parsePath(path) + if not self.server.site_manager.get(match.group("address")) and (not user or user.master_address not in local_master_addresses): + self.sendHeader(404) + return self.formatError("Not Found", "Adding new sites disabled on this proxy", details=True) + + if user_created: + if not extra_headers: + extra_headers = {} + extra_headers['Set-Cookie'] = "master_address=%s;path=/;max-age=2592000;" % user.master_address # = 30 days + + loggedin = self.get.get("login") == "done" + + back_generator = super(UiRequestPlugin, self).actionWrapper(path, extra_headers) # Get the wrapper frame output + + if not back_generator: # Wrapper error or not string returned, injection not possible + return False + + elif loggedin: + back = next(back_generator) + inject_html = """ + + + + + """.replace("\t", "") + if user.master_address in local_master_addresses: + message = "Hello master!" + else: + message = "Hello again!" + inject_html = inject_html.replace("{message}", message) + inject_html = inject_html.replace("{script_nonce}", self.getScriptNonce()) + return iter([re.sub(b"\s*\s*$", inject_html.encode(), back)]) # Replace the tags with the injection + + else: # No injection necessary + return back_generator + + # Get the current user based on request's cookies + # Return: User object or None if no match + def getCurrentUser(self): + cookies = self.getCookies() + user = None + if "master_address" in cookies: + users = self.user_manager.list() + user = users.get(cookies["master_address"]) + return user + + +@PluginManager.registerTo("UiWebsocket") +class UiWebsocketPlugin(object): + def __init__(self, *args, **kwargs): + if config.multiuser_no_new_sites: + flag.no_multiuser(self.actionMergerSiteAdd) + + super(UiWebsocketPlugin, self).__init__(*args, **kwargs) + + # Let the page know we running in multiuser mode + def formatServerInfo(self): + server_info = super(UiWebsocketPlugin, self).formatServerInfo() + server_info["multiuser"] = True + if "ADMIN" in self.site.settings["permissions"]: + server_info["master_address"] = self.user.master_address + is_multiuser_admin = config.multiuser_local or self.user.master_address in local_master_addresses + server_info["multiuser_admin"] = is_multiuser_admin + return server_info + + # Show current user's master seed + @flag.admin + def actionUserShowMasterSeed(self, to): + message = "Your unique private key:" + message += "
%s
" % self.user.master_seed + message += "(Save it, you can access your account using this information)" + self.cmd("notification", ["info", message]) + + # Logout user + @flag.admin + def actionUserLogout(self, to): + message = "You have been logged out. Login to another account" + self.cmd("notification", ["done", message, 1000000]) # 1000000 = Show ~forever :) + + script = "document.cookie = 'master_address=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';" + script += "$('#button_notification').on('click', function() { epixframe.cmd(\"userLoginForm\", []); });" + self.cmd("injectScript", script) + # Delete from user_manager + user_manager = UserManager.user_manager + if self.user.master_address in user_manager.users: + if not config.multiuser_local: + del user_manager.users[self.user.master_address] + self.response(to, "Successful logout") + else: + self.response(to, "User not found") + + @flag.admin + def actionUserSet(self, to, master_address): + user_manager = UserManager.user_manager + user = user_manager.get(master_address) + if not user: + raise Exception("No user found") + + script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % master_address + script += "epixframe.cmd('wrapperReload', ['login=done']);" + self.cmd("notification", ["done", "Successful login, reloading page..."]) + self.cmd("injectScript", script) + + self.response(to, "ok") + + @flag.admin + def actionUserSelectForm(self, to): + if not config.multiuser_local: + raise Exception("Only allowed in multiuser local mode") + user_manager = UserManager.user_manager + body = "" + "Change account:" + "" + for master_address, user in user_manager.list().items(): + is_active = self.user.master_address == master_address + if user.certs: + first_cert = next(iter(user.certs.keys())) + title = "%s@%s" % (user.certs[first_cert]["auth_user_name"], first_cert) + else: + title = user.master_address + if len(user.sites) < 2 and not is_active: # Avoid listing ad-hoc created users + continue + if is_active: + css_class = "active" + else: + css_class = "noclass" + body += "%s" % (css_class, user.master_address, title) + + script = """ + $(".notification .select.user").on("click", function() { + $(".notification .select").removeClass('active') + epixframe.response(%s, this.title) + return false + }) + """ % self.next_message_id + + self.cmd("notification", ["ask", body], lambda master_address: self.actionUserSet(to, master_address)) + self.cmd("injectScript", script) + + # Show login form + def actionUserLoginForm(self, to): + self.cmd("prompt", ["Login
Your private key:", "password", "Login"], self.responseUserLogin) + + # Login form submit + def responseUserLogin(self, master_seed): + user_manager = UserManager.user_manager + user = user_manager.get(CryptEpix.privatekeyToAddress(master_seed)) + if not user: + user = user_manager.create(master_seed=master_seed) + if user.master_address: + script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % user.master_address + script += "epixframe.cmd('wrapperReload', ['login=done']);" + self.cmd("notification", ["done", "Successful login, reloading page..."]) + self.cmd("injectScript", script) + else: + self.cmd("notification", ["error", "Error: Invalid master seed"]) + self.actionUserLoginForm(0) + + def hasCmdPermission(self, cmd): + flags = flag.db.get(self.getCmdFuncName(cmd), ()) + is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses + if is_public_proxy_user and "no_multiuser" in flags: + self.cmd("notification", ["info", _("This function ({cmd}) is disabled on this proxy!")]) + return False + else: + return super(UiWebsocketPlugin, self).hasCmdPermission(cmd) + + def actionCertAdd(self, *args, **kwargs): + super(UiWebsocketPlugin, self).actionCertAdd(*args, **kwargs) + master_seed = self.user.master_seed + message = """ + + Hello, welcome to ZeroProxy!
A new, unique account created for you:
+ + +
+ This is your private key, save it, so you can login next time.
+ Warning: Without this key, your account will be lost forever! +

+ Ok, Saved it!

+ This site allows you to browse EpixNet content, but if you want to secure your account
+ and help to keep the network alive, then please run your own EpixNet client.
+ """ + + self.cmd("notification", ["info", message]) + + script = """ + $("#button_notification_masterseed").on("click", function() { + this.value = "{master_seed}"; this.setSelectionRange(0,100); + }) + $("#button_notification_download").on("mousedown", function() { + this.href = window.URL.createObjectURL(new Blob(["EpixNet user master seed:\\r\\n{master_seed}"])) + }) + """.replace("{master_seed}", master_seed) + self.cmd("injectScript", script) + + def actionPermissionAdd(self, to, permission): + is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses + if permission == "NOSANDBOX" and is_public_proxy_user: + self.cmd("notification", ["info", "You can't disable sandbox on this proxy!"]) + self.response(to, {"error": "Denied by proxy"}) + return False + else: + return super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission) + + +@PluginManager.registerTo("ConfigPlugin") +class ConfigPlugin(object): + def createArguments(self): + group = self.parser.add_argument_group("Multiuser plugin") + group.add_argument('--multiuser-local', help="Enable unsafe Ui functions and write users to disk", action='store_true') + group.add_argument('--multiuser-no-new-sites', help="Denies adding new sites by normal users", action='store_true') + + return super(ConfigPlugin, self).createArguments() From ba5c8fdb78adc066e3d9ba219f0547914031fcde Mon Sep 17 00:00:00 2001 From: mx5kevin <47847121+mx5kevin@users.noreply.github.com> Date: Fri, 15 May 2026 09:49:42 +0000 Subject: [PATCH 3/6] Multiuser plugin redame Multiuser plugin redame --- plugins/disabled-Multiuser/MULTIUSER_README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 plugins/disabled-Multiuser/MULTIUSER_README.md diff --git a/plugins/disabled-Multiuser/MULTIUSER_README.md b/plugins/disabled-Multiuser/MULTIUSER_README.md new file mode 100644 index 000000000..1d43e5039 --- /dev/null +++ b/plugins/disabled-Multiuser/MULTIUSER_README.md @@ -0,0 +1,16 @@ +# Multiuser Plugin + +**Main Features:** +- Mode settings: access the entire network, or allow only listed sites. +- Creation of new sites can be allowed or blocked (`multiuser_no_new_sites`). + - `True` → creation of new sites **allowed** (default) + - `False` → creation of new sites **blocked**, in which case the user receives the following **error message**: + ``` + Not Found + Adding new sites disabled on this proxy + ``` +- Users listed in `local_master_addresses` can **always create new sites**. + +**Configuration and Editing:** +- Open the **`MultiuserPlugin.py`** file in Notepad++ or any other text editor. +- Change the `config.multiuser_no_new_sites` value according to your desired behavior. \ No newline at end of file From b411b4abd5f247b3da51a00b918d299471c36e5a Mon Sep 17 00:00:00 2001 From: mx5kevin <47847121+mx5kevin@users.noreply.github.com> Date: Fri, 15 May 2026 10:32:31 +0000 Subject: [PATCH 4/6] Add files via upload --- plugins/disabled-Multiuser/MULTIUSER_README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/disabled-Multiuser/MULTIUSER_README.md b/plugins/disabled-Multiuser/MULTIUSER_README.md index 1d43e5039..707a86cfb 100644 --- a/plugins/disabled-Multiuser/MULTIUSER_README.md +++ b/plugins/disabled-Multiuser/MULTIUSER_README.md @@ -2,14 +2,14 @@ **Main Features:** - Mode settings: access the entire network, or allow only listed sites. -- Creation of new sites can be allowed or blocked (`multiuser_no_new_sites`). +- Adding existing sites can be allowed or blocked (`multiuser_no_new_sites`). - `True` → creation of new sites **allowed** (default) - `False` → creation of new sites **blocked**, in which case the user receives the following **error message**: ``` Not Found Adding new sites disabled on this proxy ``` -- Users listed in `local_master_addresses` can **always create new sites**. +- Users listed in `local_master_addresses` can **always add sites.**. **Configuration and Editing:** - Open the **`MultiuserPlugin.py`** file in Notepad++ or any other text editor. From 2c5cb80f57805ac24cbfb8e7eb492e60b74e07c7 Mon Sep 17 00:00:00 2001 From: mx5kevin <47847121+mx5kevin@users.noreply.github.com> Date: Fri, 15 May 2026 10:39:14 +0000 Subject: [PATCH 5/6] Add files via upload --- plugins/disabled-Multiuser/MULTIUSER_README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/disabled-Multiuser/MULTIUSER_README.md b/plugins/disabled-Multiuser/MULTIUSER_README.md index 707a86cfb..08fcfdaba 100644 --- a/plugins/disabled-Multiuser/MULTIUSER_README.md +++ b/plugins/disabled-Multiuser/MULTIUSER_README.md @@ -3,8 +3,8 @@ **Main Features:** - Mode settings: access the entire network, or allow only listed sites. - Adding existing sites can be allowed or blocked (`multiuser_no_new_sites`). - - `True` → creation of new sites **allowed** (default) - - `False` → creation of new sites **blocked**, in which case the user receives the following **error message**: + - `True` → adding existing sites allowed (default) **allowed** (default) + - `False` → adding existing sites allowed (default) **blocked**, in which case the user receives the following **error message**: ``` Not Found Adding new sites disabled on this proxy From 3bd2add184cfad9305b035a99f2b5a095bb646ca Mon Sep 17 00:00:00 2001 From: mx5kevin <47847121+mx5kevin@users.noreply.github.com> Date: Fri, 15 May 2026 10:56:20 +0000 Subject: [PATCH 6/6] Add files via upload --- plugins/disabled-Multiuser/MULTIUSER_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/disabled-Multiuser/MULTIUSER_README.md b/plugins/disabled-Multiuser/MULTIUSER_README.md index 08fcfdaba..e593543d6 100644 --- a/plugins/disabled-Multiuser/MULTIUSER_README.md +++ b/plugins/disabled-Multiuser/MULTIUSER_README.md @@ -4,7 +4,7 @@ - Mode settings: access the entire network, or allow only listed sites. - Adding existing sites can be allowed or blocked (`multiuser_no_new_sites`). - `True` → adding existing sites allowed (default) **allowed** (default) - - `False` → adding existing sites allowed (default) **blocked**, in which case the user receives the following **error message**: + - `False` → adding existing sites not allowed **blocked**, in which case the user receives the following **error message**: ``` Not Found Adding new sites disabled on this proxy