Encode config params in URL (#842)
Adds support for encoding (and optionally encrypting) user config values as a single string that can be passed to any endpoint with the "preferences" url param. Co-authored-by: Ben Busby <contact@benbusby.com>
This commit is contained in:
		
							parent
							
								
									11275a7796
								
							
						
					
					
						commit
						74503d542e
					
				| 
						 | 
				
			
			@ -14,7 +14,7 @@ RUN pip install --prefix /install --no-warn-script-location --no-cache-dir -r re
 | 
			
		|||
 | 
			
		||||
FROM python:3.11.0a5-alpine
 | 
			
		||||
 | 
			
		||||
RUN apk add --update --no-cache tor curl openrc
 | 
			
		||||
RUN apk add --update --no-cache tor curl openrc libstdc++
 | 
			
		||||
# libcurl4-openssl-dev
 | 
			
		||||
 | 
			
		||||
RUN apk -U upgrade
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										36
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								README.md
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -389,23 +389,25 @@ There are a few optional environment variables available for customizing a Whoog
 | 
			
		|||
### Config Environment Variables
 | 
			
		||||
These environment variables allow setting default config values, but can be overwritten manually by using the home page config menu. These allow a shortcut for destroying/rebuilding an instance to the same config state every time.
 | 
			
		||||
 | 
			
		||||
| Variable                       | Description                                                     |
 | 
			
		||||
| ------------------------------ | --------------------------------------------------------------- |
 | 
			
		||||
| WHOOGLE_CONFIG_DISABLE         | Hide config from UI and disallow changes to config by client    |
 | 
			
		||||
| WHOOGLE_CONFIG_COUNTRY         | Filter results by hosting country                               |
 | 
			
		||||
| WHOOGLE_CONFIG_LANGUAGE        | Set interface language                                          |
 | 
			
		||||
| WHOOGLE_CONFIG_SEARCH_LANGUAGE | Set search result language                                      |
 | 
			
		||||
| WHOOGLE_CONFIG_BLOCK           | Block websites from search results (use comma-separated list)   |
 | 
			
		||||
| WHOOGLE_CONFIG_THEME           | Set theme mode (light, dark, or system)                         |
 | 
			
		||||
| WHOOGLE_CONFIG_SAFE            | Enable safe searches                                            |
 | 
			
		||||
| WHOOGLE_CONFIG_ALTS            | Use social media site alternatives (nitter, invidious, etc)     |
 | 
			
		||||
| WHOOGLE_CONFIG_NEAR            | Restrict results to only those near a particular city           |
 | 
			
		||||
| WHOOGLE_CONFIG_TOR             | Use Tor routing (if available)                                  |
 | 
			
		||||
| WHOOGLE_CONFIG_NEW_TAB         | Always open results in new tab                                  |
 | 
			
		||||
| WHOOGLE_CONFIG_VIEW_IMAGE      | Enable View Image option                                        |
 | 
			
		||||
| WHOOGLE_CONFIG_GET_ONLY        | Search using GET requests only                                  |
 | 
			
		||||
| WHOOGLE_CONFIG_URL             | The root url of the instance (`https://<your url>/`)            |
 | 
			
		||||
| WHOOGLE_CONFIG_STYLE           | The custom CSS to use for styling (should be single line)       |
 | 
			
		||||
| Variable                             | Description                                                     |
 | 
			
		||||
| ------------------------------------ | --------------------------------------------------------------- |
 | 
			
		||||
| WHOOGLE_CONFIG_DISABLE               | Hide config from UI and disallow changes to config by client    |
 | 
			
		||||
| WHOOGLE_CONFIG_COUNTRY               | Filter results by hosting country                               |
 | 
			
		||||
| WHOOGLE_CONFIG_LANGUAGE              | Set interface language                                          |
 | 
			
		||||
| WHOOGLE_CONFIG_SEARCH_LANGUAGE       | Set search result language                                      |
 | 
			
		||||
| WHOOGLE_CONFIG_BLOCK                 | Block websites from search results (use comma-separated list)   |
 | 
			
		||||
| WHOOGLE_CONFIG_THEME                 | Set theme mode (light, dark, or system)                         |
 | 
			
		||||
| WHOOGLE_CONFIG_SAFE                  | Enable safe searches                                            |
 | 
			
		||||
| WHOOGLE_CONFIG_ALTS                  | Use social media site alternatives (nitter, invidious, etc)     |
 | 
			
		||||
| WHOOGLE_CONFIG_NEAR                  | Restrict results to only those near a particular city           |
 | 
			
		||||
| WHOOGLE_CONFIG_TOR                   | Use Tor routing (if available)                                  |
 | 
			
		||||
| WHOOGLE_CONFIG_NEW_TAB               | Always open results in new tab                                  |
 | 
			
		||||
| WHOOGLE_CONFIG_VIEW_IMAGE            | Enable View Image option                                        |
 | 
			
		||||
| WHOOGLE_CONFIG_GET_ONLY              | Search using GET requests only                                  |
 | 
			
		||||
| WHOOGLE_CONFIG_URL                   | The root url of the instance (`https://<your url>/`)            |
 | 
			
		||||
| WHOOGLE_CONFIG_STYLE                 | The custom CSS to use for styling (should be single line)       |
 | 
			
		||||
| WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED | Encrypt preferences token, requires preferences key             |
 | 
			
		||||
| WHOOGLE_CONFIG_PREFERENCES_KEY       | Key to encrypt preferences in URL (REQUIRED to show url)        |
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
Same as most search engines, with the exception of filtering by time range.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								app.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								app.json
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -174,6 +174,16 @@
 | 
			
		|||
        "description": "[CONFIG] Custom CSS styling (paste in CSS or leave blank)",
 | 
			
		||||
        "value": ":root { /* LIGHT THEME COLORS */ --whoogle-background: #d8dee9; --whoogle-accent: #2e3440; --whoogle-text: #3B4252; --whoogle-contrast-text: #eceff4; --whoogle-secondary-text: #70757a; --whoogle-result-bg: #fff; --whoogle-result-title: #4c566a; --whoogle-result-url: #81a1c1; --whoogle-result-visited: #a3be8c; /* DARK THEME COLORS */ --whoogle-dark-background: #222; --whoogle-dark-accent: #685e79; --whoogle-dark-text: #fff; --whoogle-dark-contrast-text: #000; --whoogle-dark-secondary-text: #bbb; --whoogle-dark-result-bg: #000; --whoogle-dark-result-title: #1967d2; --whoogle-dark-result-url: #4b11a8; --whoogle-dark-result-visited: #bbbbff; }",
 | 
			
		||||
        "required": false
 | 
			
		||||
    },
 | 
			
		||||
    "WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED": {
 | 
			
		||||
        "description": "[CONFIG] Encrypt preferences token, requires WHOOGLE_CONFIG_PREFERENCES_KEY to be set",
 | 
			
		||||
        "value": "",
 | 
			
		||||
        "required": false
 | 
			
		||||
    },
 | 
			
		||||
    "WHOOGLE_CONFIG_PREFERENCES_KEY": {
 | 
			
		||||
        "description": "[CONFIG] Key to encrypt preferences",
 | 
			
		||||
        "value": "NEEDS_TO_BE_MODIFIED",
 | 
			
		||||
        "required": false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,13 @@
 | 
			
		|||
from inspect import Attribute
 | 
			
		||||
from app.utils.misc import read_config_bool
 | 
			
		||||
from flask import current_app
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
from base64 import urlsafe_b64encode, urlsafe_b64decode
 | 
			
		||||
import pickle
 | 
			
		||||
from cryptography.fernet import Fernet
 | 
			
		||||
import hashlib
 | 
			
		||||
import brotli
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Config:
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +35,9 @@ class Config:
 | 
			
		|||
        self.view_image = read_config_bool('WHOOGLE_CONFIG_VIEW_IMAGE')
 | 
			
		||||
        self.get_only = read_config_bool('WHOOGLE_CONFIG_GET_ONLY')
 | 
			
		||||
        self.anon_view = read_config_bool('WHOOGLE_CONFIG_ANON_VIEW')
 | 
			
		||||
        self.preferences_encrypted = read_config_bool('WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED')
 | 
			
		||||
        self.preferences_key = os.getenv('WHOOGLE_CONFIG_PREFERENCES_KEY', '')
 | 
			
		||||
        
 | 
			
		||||
        self.accept_language = False
 | 
			
		||||
 | 
			
		||||
        self.safe_keys = [
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +51,8 @@ class Config:
 | 
			
		|||
            'block',
 | 
			
		||||
            'safe',
 | 
			
		||||
            'nojs',
 | 
			
		||||
            'anon_view'
 | 
			
		||||
            'anon_view',
 | 
			
		||||
            'preferences_encrypted'
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        # Skip setting custom config if there isn't one
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +81,24 @@ class Config:
 | 
			
		|||
                if not name.startswith("__")
 | 
			
		||||
                and (type(attr) is bool or type(attr) is str)}
 | 
			
		||||
 | 
			
		||||
    def get_attrs(self):
 | 
			
		||||
        return {name: attr for name, attr in self.__dict__.items()
 | 
			
		||||
                if not name.startswith("__")
 | 
			
		||||
                and (type(attr) is bool or type(attr) is str)}
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def preferences(self) -> str:
 | 
			
		||||
        # if encryption key is not set will uncheck preferences encryption
 | 
			
		||||
        if self.preferences_encrypted:
 | 
			
		||||
            self.preferences_encrypted = bool(self.preferences_key)
 | 
			
		||||
        
 | 
			
		||||
        # add a tag for visibility if preferences token startswith 'e' it means
 | 
			
		||||
        # the token is encrypted, 'u' means the token is unencrypted and can be
 | 
			
		||||
        # used by other whoogle instances
 | 
			
		||||
        encrypted_flag = "e" if self.preferences_encrypted else 'u'
 | 
			
		||||
        preferences_digest = self._encode_preferences()
 | 
			
		||||
        return f"{encrypted_flag}{preferences_digest}"
 | 
			
		||||
 | 
			
		||||
    def is_safe_key(self, key) -> bool:
 | 
			
		||||
        """Establishes a group of config options that are safe to set
 | 
			
		||||
        in the url.
 | 
			
		||||
| 
						 | 
				
			
			@ -109,6 +137,13 @@ class Config:
 | 
			
		|||
        Returns:
 | 
			
		||||
            Config -- a modified config object
 | 
			
		||||
        """
 | 
			
		||||
        if 'preferences' in params:
 | 
			
		||||
            params_new = self._decode_preferences(params['preferences'])
 | 
			
		||||
            # if preferences leads to an empty dictionary it means preferences
 | 
			
		||||
            # parameter was not decrypted successfully
 | 
			
		||||
            if len(params_new):
 | 
			
		||||
                params = params_new 
 | 
			
		||||
 | 
			
		||||
        for param_key in params.keys():
 | 
			
		||||
            if not self.is_safe_key(param_key):
 | 
			
		||||
                continue
 | 
			
		||||
| 
						 | 
				
			
			@ -116,8 +151,9 @@ class Config:
 | 
			
		|||
 | 
			
		||||
            if param_val == 'off':
 | 
			
		||||
                param_val = False
 | 
			
		||||
            elif param_val.isdigit():
 | 
			
		||||
                param_val = int(param_val)
 | 
			
		||||
            elif isinstance(param_val, str):
 | 
			
		||||
                if param_val.isdigit():
 | 
			
		||||
                    param_val = int(param_val)
 | 
			
		||||
 | 
			
		||||
            self[param_key] = param_val
 | 
			
		||||
        return self
 | 
			
		||||
| 
						 | 
				
			
			@ -135,3 +171,40 @@ class Config:
 | 
			
		|||
            param_str = param_str + f'&{safe_key}={self[safe_key]}'
 | 
			
		||||
 | 
			
		||||
        return param_str
 | 
			
		||||
 | 
			
		||||
    def _get_fernet_key(self, password: str) -> bytes:
 | 
			
		||||
        hash_object = hashlib.md5(password.encode())
 | 
			
		||||
        key = urlsafe_b64encode(hash_object.hexdigest().encode())
 | 
			
		||||
        return key
 | 
			
		||||
 | 
			
		||||
    def _encode_preferences(self) -> str:
 | 
			
		||||
        encoded_preferences = brotli.compress(pickle.dumps(self.get_attrs()))
 | 
			
		||||
        if self.preferences_encrypted:
 | 
			
		||||
            if self.preferences_key != '':
 | 
			
		||||
                key = self._get_fernet_key(self.preferences_key)
 | 
			
		||||
                encoded_preferences = Fernet(key).encrypt(encoded_preferences)
 | 
			
		||||
                encoded_preferences = brotli.compress(encoded_preferences)
 | 
			
		||||
 | 
			
		||||
        return urlsafe_b64encode(encoded_preferences).decode()
 | 
			
		||||
 | 
			
		||||
    def _decode_preferences(self, preferences: str) -> dict:
 | 
			
		||||
        mode = preferences[0]
 | 
			
		||||
        preferences = preferences[1:]
 | 
			
		||||
        if mode == 'e': # preferences are encrypted
 | 
			
		||||
            try:
 | 
			
		||||
                key = self._get_fernet_key(self.preferences_key)
 | 
			
		||||
 | 
			
		||||
                config = Fernet(key).decrypt(
 | 
			
		||||
                    brotli.decompress(urlsafe_b64decode(preferences.encode()))
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                config = pickle.loads(brotli.decompress(config))
 | 
			
		||||
            except Exception:
 | 
			
		||||
                config = {}
 | 
			
		||||
        elif mode == 'u': # preferences are not encrypted
 | 
			
		||||
            config = pickle.loads(
 | 
			
		||||
                brotli.decompress(urlsafe_b64decode(preferences.encode()))
 | 
			
		||||
            )
 | 
			
		||||
        else: # preferences are incorrectly formatted
 | 
			
		||||
            config = {}
 | 
			
		||||
        return config
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -193,6 +193,9 @@ def index():
 | 
			
		|||
        session['error_message'] = ''
 | 
			
		||||
        return render_template('error.html', error_message=error_message)
 | 
			
		||||
 | 
			
		||||
    # Update user config if specified in search args
 | 
			
		||||
    g.user_config = g.user_config.from_params(g.request_params)
 | 
			
		||||
 | 
			
		||||
    return render_template('index.html',
 | 
			
		||||
                           has_update=app.config['HAS_UPDATE'],
 | 
			
		||||
                           languages=app.config['LANGUAGES'],
 | 
			
		||||
| 
						 | 
				
			
			@ -231,7 +234,8 @@ def opensearch():
 | 
			
		|||
        main_url=opensearch_url,
 | 
			
		||||
        request_type='' if get_only else 'method="post"',
 | 
			
		||||
        search_type=request.args.get('tbm'),
 | 
			
		||||
        search_name=get_search_name(request.args.get('tbm'))
 | 
			
		||||
        search_name=get_search_name(request.args.get('tbm')),
 | 
			
		||||
        preferences=g.user_config.preferences
 | 
			
		||||
    ), 200, {'Content-Type': 'application/xml'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -334,6 +338,7 @@ def search():
 | 
			
		|||
    tabs = get_tabs_content(app.config['HEADER_TABS'],
 | 
			
		||||
                            search_util.full_query,
 | 
			
		||||
                            search_util.search_type,
 | 
			
		||||
                            g.user_config.preferences,
 | 
			
		||||
                            translation)
 | 
			
		||||
 | 
			
		||||
    # Feature to display currency_card
 | 
			
		||||
| 
						 | 
				
			
			@ -342,6 +347,9 @@ def search():
 | 
			
		|||
        html_soup = bsoup(str(response), 'html.parser')
 | 
			
		||||
        response = add_currency_card(html_soup, conversion)
 | 
			
		||||
 | 
			
		||||
    preferences = g.user_config.preferences
 | 
			
		||||
    home_url = f"home?preferences={preferences}" if preferences else "home"
 | 
			
		||||
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'display.html',
 | 
			
		||||
        has_update=app.config['HAS_UPDATE'],
 | 
			
		||||
| 
						 | 
				
			
			@ -365,6 +373,7 @@ def search():
 | 
			
		|||
        version_number=app.config['VERSION_NUMBER'],
 | 
			
		||||
        search_header=render_template(
 | 
			
		||||
            'header.html',
 | 
			
		||||
            home_url=home_url,
 | 
			
		||||
            config=g.user_config,
 | 
			
		||||
            translation=translation,
 | 
			
		||||
            languages=app.config['LANGUAGES'],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,9 @@
 | 
			
		|||
        "config-tor": "Use Tor",
 | 
			
		||||
        "config-get-only": "GET Requests Only",
 | 
			
		||||
        "config-url": "Root URL",
 | 
			
		||||
        "config-pref-url": "Preferences URL",
 | 
			
		||||
        "config-pref-encryption": "Encrypt Preferences",
 | 
			
		||||
        "config-pref-help": "Requires WHOOGLE_CONFIG_PREFERENCES_KEY, otherwise this will be ignored.",
 | 
			
		||||
        "config-css": "Custom CSS",
 | 
			
		||||
        "load": "Load",
 | 
			
		||||
        "apply": "Apply",
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +75,9 @@
 | 
			
		|||
        "config-tor": "Gebruik Tor",
 | 
			
		||||
        "config-get-only": "Alleen GET Requests",
 | 
			
		||||
        "config-url": "Root URL",
 | 
			
		||||
        "config-pref-url": "Voorkeurs URL",
 | 
			
		||||
        "config-pref-encryption": "Versleutel voorkeuren",
 | 
			
		||||
        "config-pref-help": "Vereist WHOOGLE_CONFIG_PREFERENCES_KEY, anders wordt dit genegeerd.",
 | 
			
		||||
        "config-css": "Eigen CSS",
 | 
			
		||||
        "load": "Laden",
 | 
			
		||||
        "apply": "Opslaan",
 | 
			
		||||
| 
						 | 
				
			
			@ -118,6 +124,9 @@
 | 
			
		|||
        "config-tor": "Tor benutzen",
 | 
			
		||||
        "config-get-only": "Auschließlich GET-Anfragen",
 | 
			
		||||
        "config-url": "Root URL",
 | 
			
		||||
        "config-pref-url": "Einstellungs URL",
 | 
			
		||||
        "config-pref-encryption": "Einstellungen verschlüsseln",
 | 
			
		||||
        "config-pref-help": "Erfordert WHOOGLE_CONFIG_PREFERENCES_KEY, sonst wird dies ignoriert.",
 | 
			
		||||
        "config-css": "Custom CSS",
 | 
			
		||||
        "load": "Laden",
 | 
			
		||||
        "apply": "Übernehmen",
 | 
			
		||||
| 
						 | 
				
			
			@ -164,6 +173,9 @@
 | 
			
		|||
        "config-tor": "Usa Tor",
 | 
			
		||||
        "config-get-only": "GET solo solicitudes",
 | 
			
		||||
        "config-url": "URL raíz",
 | 
			
		||||
        "config-pref-url": "URL de preferencias",
 | 
			
		||||
        "config-pref-encryption": "Cifrar preferencias",
 | 
			
		||||
        "config-pref-help": "Requiere WHOOGLE_CONFIG_PREFERENCES_KEY; de lo contrario, se ignorará.",
 | 
			
		||||
        "config-css": "CSS personalizado",
 | 
			
		||||
        "load": "Cargar",
 | 
			
		||||
        "apply": "Aplicar",
 | 
			
		||||
| 
						 | 
				
			
			@ -210,6 +222,9 @@
 | 
			
		|||
        "config-tor": "Usa Tor",
 | 
			
		||||
        "config-get-only": "Utilizza solo richieste GET",
 | 
			
		||||
        "config-url": "Root URL",
 | 
			
		||||
        "config-pref-url": "URL delle preferenze",
 | 
			
		||||
        "config-pref-encryption": "Crittografa le preferenze",
 | 
			
		||||
        "config-pref-help": "Richiede WHOOGLE_CONFIG_PREFERENCES_KEY, altrimenti verrà ignorato.",
 | 
			
		||||
        "config-css": "CSS Personalizzato",
 | 
			
		||||
        "load": "Carica",
 | 
			
		||||
        "apply": "Applica",
 | 
			
		||||
| 
						 | 
				
			
			@ -256,6 +271,9 @@
 | 
			
		|||
        "config-tor": "Usar Tor",
 | 
			
		||||
        "config-get-only": "Apenas Pedidos GET",
 | 
			
		||||
        "config-url": "URL Fonte",
 | 
			
		||||
        "config-pref-url": "URL de preferências",
 | 
			
		||||
        "config-pref-encryption": "Criptografar preferências",
 | 
			
		||||
        "config-pref-help": "Requer WHOOGLE_CONFIG_PREFERENCES_KEY, caso contrário, será ignorado.",
 | 
			
		||||
        "config-css": "CSS Personalizado",
 | 
			
		||||
        "load": "Carregar",
 | 
			
		||||
        "apply": "Aplicar",
 | 
			
		||||
| 
						 | 
				
			
			@ -302,6 +320,9 @@
 | 
			
		|||
        "config-tor": "Использовать Tor",
 | 
			
		||||
        "config-get-only": "Только GET-запросы",
 | 
			
		||||
        "config-url": "Корневой URL-адрес",
 | 
			
		||||
        "config-pref-url": "URL-адрес настроек",
 | 
			
		||||
        "config-pref-encryption": "Зашифровать настройки",
 | 
			
		||||
        "config-pref-help": "Требуется WHOOGLE_CONFIG_PREFERENCES_KEY, иначе это будет проигнорировано.",
 | 
			
		||||
        "config-css": "Пользовательский CSS",
 | 
			
		||||
        "load": "Загрузить",
 | 
			
		||||
        "apply": "Применить",
 | 
			
		||||
| 
						 | 
				
			
			@ -348,6 +369,9 @@
 | 
			
		|||
        "config-tor": "使用 Tor",
 | 
			
		||||
        "config-get-only": "仅限 GET 请求",
 | 
			
		||||
        "config-url": "站点根 URL",
 | 
			
		||||
        "config-pref-url": "首选项网址",
 | 
			
		||||
        "config-pref-encryption": "加密首选项",
 | 
			
		||||
        "config-pref-help": "需要 WHOOGLE_CONFIG_PREFERENCES_KEY,否则将被忽略。",
 | 
			
		||||
        "config-css": "自定义 CSS",
 | 
			
		||||
        "load": "载入",
 | 
			
		||||
        "apply": "应用",
 | 
			
		||||
| 
						 | 
				
			
			@ -394,6 +418,9 @@
 | 
			
		|||
        "config-tor": "ටෝර් භාවිතා කරන්න",
 | 
			
		||||
        "config-get-only": "ඉල්ලීම් පමණක් ලබා ගන්න",
 | 
			
		||||
        "config-url": "ඒ.ස.නි.(URL) මූලය",
 | 
			
		||||
        "config-pref-url": "මනාප URL",
 | 
			
		||||
        "config-pref-encryption": "මනාප සංකේතනය කරන්න",
 | 
			
		||||
        "config-pref-help": "WHOOGLE_CONFIG_PREFERENCES_KEY අවශ්ය වේ, එසේ නොමැතිනම් මෙය නොසලකා හරිනු ඇත.",
 | 
			
		||||
        "config-css": "අභිරුචි සීඑස්එස්",
 | 
			
		||||
        "load": "පූරනය කරන්න",
 | 
			
		||||
        "apply": "යොදන්න",
 | 
			
		||||
| 
						 | 
				
			
			@ -440,6 +467,9 @@
 | 
			
		|||
        "config-tor": "Utiliser Tor",
 | 
			
		||||
        "config-get-only": "Requêtes GET seulement",
 | 
			
		||||
        "config-url": "URL de la racine",
 | 
			
		||||
        "config-pref-url": "URL des préférences",
 | 
			
		||||
        "config-pref-encryption": "Chiffrer les préférences",
 | 
			
		||||
        "config-pref-help": "Nécessite WHOOGLE_CONFIG_PREFERENCES_KEY, sinon cela sera ignoré.",
 | 
			
		||||
        "config-css": "CSS Personalisé",
 | 
			
		||||
        "load": "Charger",
 | 
			
		||||
        "apply": "Appliquer",
 | 
			
		||||
| 
						 | 
				
			
			@ -486,6 +516,9 @@
 | 
			
		|||
        "config-tor": "استفاده از تور",
 | 
			
		||||
        "config-get-only": "فقط درخواستهای GET",
 | 
			
		||||
        "config-url": "آدرس ریشهی سایت",
 | 
			
		||||
        "config-pref-url": "URL تنظیمات برگزیده",
 | 
			
		||||
        "config-pref-encryption": "رمزگذاری تنظیمات برگزیده",
 | 
			
		||||
        "config-pref-help": "به WHOOGLE_CONFIG_PREFERENCES_KEY نیاز دارد، در غیر این صورت نادیده گرفته خواهد شد.",
 | 
			
		||||
        "config-css": "CSS دلخواه",
 | 
			
		||||
        "load": "بارگذاری",
 | 
			
		||||
        "apply": "تایید",
 | 
			
		||||
| 
						 | 
				
			
			@ -532,6 +565,9 @@
 | 
			
		|||
        "config-tor": "Používat Tor",
 | 
			
		||||
        "config-get-only": "Pouze požadavky GET",
 | 
			
		||||
        "config-url": "Kořenová adresa URL",
 | 
			
		||||
        "config-pref-url": "Adresa URL předvoleb",
 | 
			
		||||
        "config-pref-encryption": "Předvolby šifrování",
 | 
			
		||||
        "config-pref-help": "Vyžaduje WHOOGLE_CONFIG_PREFERENCES_KEY, jinak bude ignorována.",
 | 
			
		||||
        "config-css": "Vlastní CSS",
 | 
			
		||||
        "load": "Načíst",
 | 
			
		||||
        "apply": "Použít",
 | 
			
		||||
| 
						 | 
				
			
			@ -578,6 +614,9 @@
 | 
			
		|||
        "config-tor": "使用 Tor",
 | 
			
		||||
        "config-get-only": "僅限於 GET 要求",
 | 
			
		||||
        "config-url": "首頁網址",
 | 
			
		||||
        "config-pref-url": "首選項網址",
 | 
			
		||||
        "config-pref-encryption": "加密首選項",
 | 
			
		||||
        "config-pref-help": "需要 WHOOGLE_CONFIG_PREFERENCES_KEY,否則將被忽略。",
 | 
			
		||||
        "config-css": "自定 CSS",
 | 
			
		||||
        "load": "載入",
 | 
			
		||||
        "apply": "套用",
 | 
			
		||||
| 
						 | 
				
			
			@ -624,6 +663,9 @@
 | 
			
		|||
        "config-tor": "Използвайте Tor",
 | 
			
		||||
        "config-get-only": "Само GET заявки",
 | 
			
		||||
        "config-url": "Основен URL адрес",
 | 
			
		||||
        "config-pref-url": "URL адрес на предпочитанията",
 | 
			
		||||
        "config-pref-encryption": "Шифроване на предпочитанията",
 | 
			
		||||
        "config-pref-help": "Изисква WHOOGLE_CONFIG_PREFERENCES_KEY, в противен случай това ще бъде игнорирано.",
 | 
			
		||||
        "config-css": "Персонализиран CSS",
 | 
			
		||||
        "load": "Зареди",
 | 
			
		||||
        "apply": "Приложи",
 | 
			
		||||
| 
						 | 
				
			
			@ -670,6 +712,9 @@
 | 
			
		|||
        "config-tor": "TOR का प्रयोग करें",
 | 
			
		||||
        "config-get-only": "केवल GET अनुरोध",
 | 
			
		||||
        "config-url": "रूट यूआरएल",
 | 
			
		||||
        "config-pref-url": "वरीयताएँ URL",
 | 
			
		||||
        "config-pref-encryption": "एन्क्रिप्ट प्राथमिकताएं",
 | 
			
		||||
        "config-pref-help": "WHOOGLE_CONFIG_PREFERENCES_KEY की आवश्यकता है, अन्यथा इसे अनदेखा कर दिया जाएगा।",
 | 
			
		||||
        "config-css": "कस्टम सीएसएस",
 | 
			
		||||
        "load": "भार",
 | 
			
		||||
        "apply": "लागू करना",
 | 
			
		||||
| 
						 | 
				
			
			@ -716,6 +761,9 @@
 | 
			
		|||
        "config-tor": "Torを使用",
 | 
			
		||||
        "config-get-only": "GETリクエストのみ",
 | 
			
		||||
        "config-url": "ルートURL",
 | 
			
		||||
        "config-pref-url": "設定 URL",
 | 
			
		||||
        "config-pref-encryption": "設定を暗号化する",
 | 
			
		||||
        "config-pref-help": "WHOOGLE_CONFIG_PREFERENCES_KEY が必要です。それ以外の場合、これは無視されます。",
 | 
			
		||||
        "config-css": "カスタムCSS",
 | 
			
		||||
        "load": "読み込み",
 | 
			
		||||
        "apply": "反映",
 | 
			
		||||
| 
						 | 
				
			
			@ -762,6 +810,9 @@
 | 
			
		|||
        "config-tor": "Tor 사용",
 | 
			
		||||
        "config-get-only": "GET 요청만",
 | 
			
		||||
        "config-url": "루트 URL",
 | 
			
		||||
        "config-pref-url": "환경설정 URL",
 | 
			
		||||
        "config-pref-encryption": "암호화 환경 설정",
 | 
			
		||||
        "config-pref-help": "WHOOGLE_CONFIG_PREFERENCES_KEY이 필요합니다. 그렇지 않으면 무시됩니다.",
 | 
			
		||||
        "config-css": "커스텀 CSS",
 | 
			
		||||
        "load": "불러오기",
 | 
			
		||||
        "apply": "적용",
 | 
			
		||||
| 
						 | 
				
			
			@ -808,6 +859,9 @@
 | 
			
		|||
        "config-tor": "Tor bi kar bîne",
 | 
			
		||||
        "config-get-only": "Daxwazan bi Dest Bixe",
 | 
			
		||||
        "config-url": "Reha URL",
 | 
			
		||||
        "config-pref-url": "Preferences URL",
 | 
			
		||||
        "config-pref-encryption": "Vebijêrkên şîfre bikin",
 | 
			
		||||
        "config-pref-help": "WHOOGLE_CONFIG_PREFERENCES_KEY hewce dike, wekî din ev ê were paşguh kirin.",
 | 
			
		||||
        "config-css": "CSS kesane bike",
 | 
			
		||||
        "load": "Bar bike",
 | 
			
		||||
        "apply": "Bisepîne",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,13 +4,16 @@
 | 
			
		|||
            <form class="search-form header"
 | 
			
		||||
                  id="search-form"
 | 
			
		||||
                  method="{{ 'GET' if config.get_only else 'POST' }}">
 | 
			
		||||
                <a class="logo-link mobile-logo" href="home">
 | 
			
		||||
                <a class="logo-link mobile-logo" href="{{ home_url }}">
 | 
			
		||||
                    <div id="mobile-header-logo">
 | 
			
		||||
                        {{ logo|safe }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </a>
 | 
			
		||||
                <div class="H0PQec mobile-input-div">
 | 
			
		||||
                    <div class="autocomplete-mobile esbc autocomplete">
 | 
			
		||||
                        {% if config.preferences %}
 | 
			
		||||
                            <input type="hidden" name="preferences" value="{{ config.preferences }}" />
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <input
 | 
			
		||||
                                id="search-bar"
 | 
			
		||||
                                class="mobile-search-bar"
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +60,7 @@
 | 
			
		|||
{% else %}
 | 
			
		||||
    <header>
 | 
			
		||||
        <div class="logo-div">
 | 
			
		||||
            <a class="logo-link" href="home">
 | 
			
		||||
            <a class="logo-link" href="{{ home_url }}">
 | 
			
		||||
                <div class="desktop-header-logo">
 | 
			
		||||
                    {{ logo|safe }}
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +73,9 @@
 | 
			
		|||
                  method="{{ 'GET' if config.get_only else 'POST' }}">
 | 
			
		||||
                <div class="autocomplete header-autocomplete">
 | 
			
		||||
                    <div style="width: 100%; display: flex">
 | 
			
		||||
                        {% if config.preferences %}
 | 
			
		||||
                            <input type="hidden" name="preferences" value="{{ config.preferences }}" />
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <input
 | 
			
		||||
                                id="search-bar"
 | 
			
		||||
                                autocapitalize="none"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,6 +66,9 @@
 | 
			
		|||
    <form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}">
 | 
			
		||||
        <div class="search-fields">
 | 
			
		||||
            <div class="autocomplete">
 | 
			
		||||
                {% if config.preferences %}
 | 
			
		||||
                    <input type="hidden" name="preferences" value="{{ config.preferences }}" />
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                <input
 | 
			
		||||
                        type="text"
 | 
			
		||||
                        name="q"
 | 
			
		||||
| 
						 | 
				
			
			@ -233,6 +236,14 @@
 | 
			
		|||
                                {{ config.style.replace('\t', '') }}
 | 
			
		||||
                            </textarea>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="config-div config-div-pref-url">
 | 
			
		||||
                            <label for="config-pref-encryption">{{ translation['config-pref-encryption'] }}: </label>
 | 
			
		||||
                            <input type="checkbox" name="preferences_encrypted"
 | 
			
		||||
                                   id="config-pref-encryption" {{ 'checked' if config.preferences_encrypted and config.preferences_key else '' }}>
 | 
			
		||||
                            <div><span class="info-text"> — {{ translation['config-pref-help'] }}</span></div>
 | 
			
		||||
                            <label for="config-pref-url">{{ translation['config-pref-url'] }}: </label>
 | 
			
		||||
                            <input type="text" name="pref-url" id="config-pref-url" value="{{ config.url }}?preferences={{ config.preferences }}">
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="config-div config-buttons">
 | 
			
		||||
                        <input type="submit" id="config-load" value="{{ translation['load'] }}"> 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,9 @@
 | 
			
		|||
        {% if search_type %}
 | 
			
		||||
            <Param name="tbm" value="{{ search_type }}"/>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if preferences %}
 | 
			
		||||
            <Param name="preferences" value="{{ preferences }}"/>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </Url>
 | 
			
		||||
    <Url type="application/x-suggestions+json" {{ request_type|safe }} template="{{ main_url }}/autocomplete">
 | 
			
		||||
        <Param name="q" value="{searchTerms}"/>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -393,6 +393,7 @@ def add_currency_card(soup: BeautifulSoup,
 | 
			
		|||
def get_tabs_content(tabs: dict,
 | 
			
		||||
                     full_query: str,
 | 
			
		||||
                     search_type: str,
 | 
			
		||||
                     preferences: str,
 | 
			
		||||
                     translation: dict) -> dict:
 | 
			
		||||
    """Takes the default tabs content and updates it according to the query.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -416,6 +417,9 @@ def get_tabs_content(tabs: dict,
 | 
			
		|||
 | 
			
		||||
        if tab_content['tbm'] is not None:
 | 
			
		||||
            query = f"{query}&tbm={tab_content['tbm']}"
 | 
			
		||||
        
 | 
			
		||||
        if preferences:
 | 
			
		||||
            query = f"{query}&preferences={preferences}"
 | 
			
		||||
 | 
			
		||||
        tab_content['href'] = tab_content['href'].format(query=query)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,21 +47,23 @@ conf: {}
 | 
			
		|||
  # WHOOGLE_AUTOCOMPLETE: "" # Controls visibility of autocomplete/search suggestions. Default on -- use '0' to disable
 | 
			
		||||
  # WHOOGLE_MINIMAL: ""      # Remove everything except basic result cards from all search queries.
 | 
			
		||||
 | 
			
		||||
  # WHOOGLE_CONFIG_DISABLE: ""         # Hide config from UI and disallow changes to config by client
 | 
			
		||||
  # WHOOGLE_CONFIG_COUNTRY: ""         # Filter results by hosting country
 | 
			
		||||
  # WHOOGLE_CONFIG_LANGUAGE: ""        # Set interface language
 | 
			
		||||
  # WHOOGLE_CONFIG_SEARCH_LANGUAGE: "" # Set search result language
 | 
			
		||||
  # WHOOGLE_CONFIG_BLOCK: ""           # Block websites from search results (use comma-separated list)
 | 
			
		||||
  # WHOOGLE_CONFIG_THEME: ""           # Set theme mode (light, dark, or system)
 | 
			
		||||
  # WHOOGLE_CONFIG_SAFE: ""            # Enable safe searches
 | 
			
		||||
  # WHOOGLE_CONFIG_ALTS: ""            # Use social media site alternatives (nitter, invidious, etc)
 | 
			
		||||
  # WHOOGLE_CONFIG_NEAR: ""            # Restrict results to only those near a particular city
 | 
			
		||||
  # WHOOGLE_CONFIG_TOR: ""             # Use Tor routing (if available)
 | 
			
		||||
  # WHOOGLE_CONFIG_NEW_TAB: ""         # Always open results in new tab
 | 
			
		||||
  # WHOOGLE_CONFIG_VIEW_IMAGE: ""      # Enable View Image option
 | 
			
		||||
  # WHOOGLE_CONFIG_GET_ONLY: ""        # Search using GET requests only
 | 
			
		||||
  # WHOOGLE_CONFIG_URL: ""             # The root url of the instance (https://<your url>/)
 | 
			
		||||
  # WHOOGLE_CONFIG_STYLE: ""           # The custom CSS to use for styling (should be single line)
 | 
			
		||||
  # WHOOGLE_CONFIG_DISABLE: ""               # Hide config from UI and disallow changes to config by client
 | 
			
		||||
  # WHOOGLE_CONFIG_COUNTRY: ""               # Filter results by hosting country
 | 
			
		||||
  # WHOOGLE_CONFIG_LANGUAGE: ""              # Set interface language
 | 
			
		||||
  # WHOOGLE_CONFIG_SEARCH_LANGUAGE: ""       # Set search result language
 | 
			
		||||
  # WHOOGLE_CONFIG_BLOCK: ""                 # Block websites from search results (use comma-separated list)
 | 
			
		||||
  # WHOOGLE_CONFIG_THEME: ""                 # Set theme mode (light, dark, or system)
 | 
			
		||||
  # WHOOGLE_CONFIG_SAFE: ""                  # Enable safe searches
 | 
			
		||||
  # WHOOGLE_CONFIG_ALTS: ""                  # Use social media site alternatives (nitter, invidious, etc)
 | 
			
		||||
  # WHOOGLE_CONFIG_NEAR: ""                  # Restrict results to only those near a particular city
 | 
			
		||||
  # WHOOGLE_CONFIG_TOR: ""                   # Use Tor routing (if available)
 | 
			
		||||
  # WHOOGLE_CONFIG_NEW_TAB: ""               # Always open results in new tab
 | 
			
		||||
  # WHOOGLE_CONFIG_VIEW_IMAGE: ""            # Enable View Image option
 | 
			
		||||
  # WHOOGLE_CONFIG_GET_ONLY: ""              # Search using GET requests only
 | 
			
		||||
  # WHOOGLE_CONFIG_URL: ""                   # The root url of the instance (https://<your url>/)
 | 
			
		||||
  # WHOOGLE_CONFIG_STYLE: ""                 # The custom CSS to use for styling (should be single line)
 | 
			
		||||
  # WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED: "" # Encrypt preferences token, requires key
 | 
			
		||||
  # WHOOGLE_CONFIG_PREFERENCES_KEY: ""       # Key to encrypt preferences in URL (REQUIRED to show url) 
 | 
			
		||||
 | 
			
		||||
podAnnotations: {}
 | 
			
		||||
podSecurityContext: {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,6 +72,8 @@ services:
 | 
			
		|||
      # - WHOOGLE_CONFIG_SEARCH_LANGUAGE=lang_en
 | 
			
		||||
      # - WHOOGLE_CONFIG_GET_ONLY=1
 | 
			
		||||
      # - WHOOGLE_CONFIG_COUNTRY=FR
 | 
			
		||||
      # - WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED=1
 | 
			
		||||
      # - WHOOGLE_CONFIG_PREFERENCES_KEY="NEEDS_TO_BE_MODIFIED"
 | 
			
		||||
    #env_file: # Alternatively, load variables from whoogle.env
 | 
			
		||||
      #- whoogle.env
 | 
			
		||||
    ports:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
attrs==19.3.0
 | 
			
		||||
beautifulsoup4==4.10.0
 | 
			
		||||
brotli==1.0.9
 | 
			
		||||
cachelib==0.4.1
 | 
			
		||||
certifi==2020.4.5.1
 | 
			
		||||
cffi==1.15.0
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,20 @@ from app.models.endpoint import Endpoint
 | 
			
		|||
from app.utils.session import generate_user_key, valid_user_session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
JAPAN_PREFS = 'uG-gGIJwHdqxl6DrS3mnu_511HlQcRpxYlG03Xs-' \
 | 
			
		||||
   + '_znXNiJWI9nLOkRLkiiFwIpeUYMTGfUF5-t9fP5DGmzDLEt04DCx703j3nPf' \
 | 
			
		||||
   + '29v_RWkU7gXw_44m2oAFIaKGmYlu4Z0bKyu9k5WXfL9Dy6YKKnpcR5CiaFsG' \
 | 
			
		||||
   + 'rccNRkAPYm-eYGAFUV8M59f8StsGd_M-gHKGS9fLok7EhwBWjHxBJ2Kv8hsT' \
 | 
			
		||||
   + '87zftP2gMJOevTdNnezw2Y5WOx-ZotgeheCW1BYCFcRqatlov21PHp22NGVG' \
 | 
			
		||||
   + '8ZuBNAFW0bE99WSdyT7dUIvzeWCLJpbdSsq-3FUUZkxbRdFYlGd8vY1UgVAp' \
 | 
			
		||||
   + 'OSie2uAmpgLFXygO-VfNBBZ68Q7gAap2QtzHCiKD5cFYwH3LPgVJ-DoZvJ6k' \
 | 
			
		||||
   + 'alt34TaYiJphgiqFKV4SCeVmLWTkr0SF3xakSR78yYJU_d41D2ng-TojA9XZ' \
 | 
			
		||||
   + 'uR2ZqjSvPKOWvjimu89YhFOgJxG1Po8Henj5h9OL9VXXvdvlJwBSAKw1E3FV' \
 | 
			
		||||
   + '7UHWiglMxPblfxqou1cYckMYkFeIMCD2SBtju68mBiQh2k328XRPTsQ_ocby' \
 | 
			
		||||
   + 'cgVKnleGperqbD6crRk3Z9xE5sVCjujn9JNVI-7mqOITMZ0kntq9uJ3R5n25' \
 | 
			
		||||
   + 'Vec0TJ0P19nEtvjY0nJIrIjtnBg=='
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_generate_user_keys():
 | 
			
		||||
    key = generate_user_key()
 | 
			
		||||
    assert Fernet(key)
 | 
			
		||||
| 
						 | 
				
			
			@ -49,3 +63,16 @@ def test_query_decryption(client):
 | 
			
		|||
 | 
			
		||||
    with client.session_transaction() as session:
 | 
			
		||||
        assert valid_user_session(session)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_prefs_url(client):
 | 
			
		||||
    base_url = f'/{Endpoint.search}?q=wikipedia'
 | 
			
		||||
    rv = client.get(base_url)
 | 
			
		||||
    assert rv._status_code == 200
 | 
			
		||||
    assert b'wikipedia.org' in rv.data
 | 
			
		||||
    assert b'ja.wikipedia.org' not in rv.data
 | 
			
		||||
 | 
			
		||||
    rv = client.get(f'{base_url}&preferences={JAPAN_PREFS}')
 | 
			
		||||
    assert rv._status_code == 200
 | 
			
		||||
    assert b'ja.wikipedia.org' in rv.data
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,3 +85,9 @@
 | 
			
		|||
 | 
			
		||||
# Set custom CSS styling/theming
 | 
			
		||||
#WHOOGLE_CONFIG_STYLE=":root { /* LIGHT THEME COLORS */ --whoogle-background: #d8dee9; --whoogle-accent: #2e3440; --whoogle-text: #3B4252; --whoogle-contrast-text: #eceff4; --whoogle-secondary-text: #70757a; --whoogle-result-bg: #fff; --whoogle-result-title: #4c566a; --whoogle-result-url: #81a1c1; --whoogle-result-visited: #a3be8c; /* DARK THEME COLORS */ --whoogle-dark-background: #222; --whoogle-dark-accent: #685e79; --whoogle-dark-text: #fff; --whoogle-dark-contrast-text: #000; --whoogle-dark-secondary-text: #bbb; --whoogle-dark-result-bg: #000; --whoogle-dark-result-title: #1967d2; --whoogle-dark-result-url: #4b11a8; --whoogle-dark-result-visited: #bbbbff; }"
 | 
			
		||||
 | 
			
		||||
# Enable preferences encryption (requires key)
 | 
			
		||||
#WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED=1
 | 
			
		||||
 | 
			
		||||
# Set Key to encode config in url
 | 
			
		||||
#WHOOGLE_CONFIG_PREFERENCES_KEY="NEEDS_TO_BE_MODIFIED"
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user