Compare commits

...

18 commits
2.0.0 ... main

Author SHA1 Message Date
Renan Bernordi
f09a861cd1 novas regras de dominios, issue 2025-03-04 17:51:15 -03:00
Renan Bernordi
7d449b5229 delete sqlite 2025-03-04 17:50:07 -03:00
Renan Bernordi
5ca8403afc função de limpeza de cache 2025-02-28 17:15:10 -03:00
Renan Bernordi
91176050c0 adicionada ferramenta para limpar cache 2025-02-28 11:29:46 -03:00
Renan Bernordi
abb1966b33 ajuste (Package 'sqlite3', required by 'virtual:world', not found) 2025-02-28 11:01:09 -03:00
Renan Bernordi
badd23ba7c migrado do redis para sqlite, no futuro tera rotinas para limpar caches 2025-02-28 10:55:38 -03:00
Renan Bernordi
602fc277dd adicionado colar 2025-02-21 00:03:17 -03:00
Renan Bernordi
8f277a648e novas regras 2025-02-16 00:11:05 -03:00
Renan Bernordi
4079f568ba ajuste de regra 2025-02-13 18:59:34 -03:00
Renan Bernordi
30ad1d9113 issue 29 2025-02-09 15:07:06 -03:00
Renan Bernordi
72a5c6781f melhorias no docker compose 2025-02-08 02:07:58 -03:00
Renan Bernordi
d921dcd115 readme simplificado 2025-02-08 02:07:48 -03:00
Renan Bernordi
9a257efd46 suporte a dockerhub deploy 2025-02-08 01:46:09 -03:00
Renan Bernordi
7df2056c1d ajustes nas barras 2025-02-08 01:10:20 -03:00
Renan Bernordi
884641e58a issue 22 2025-02-08 01:10:05 -03:00
Renan Bernordi
b7921a2a97 correção para urls que terminam com extensão .php 2025-02-08 00:56:06 -03:00
Renan Altendorf
8b3aae2985
Merge pull request from demonisius/main
Update ru-ru.php
2025-02-07 16:35:23 -03:00
demonisius
6d08a3e017 Update ru-ru.php 2025-02-07 19:33:57 +03:00
30 changed files with 591 additions and 512 deletions

View file

@ -9,6 +9,7 @@ on:
env:
DOCKER_REGISTRY: ghcr.io
DOCKER_IMAGE_NAME: ${{ github.repository }}
DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_USERNAME }}/marreta
jobs:
docker-build:
@ -38,19 +39,27 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
images: |
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
${{ env.DOCKERHUB_REPOSITORY }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: 🔐 Log in to Registry
- name: 🔐 Log in to GitHub Registry
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 🔐 Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🏗️ Build and Push
uses: docker/build-push-action@v5
with:

1
.gitignore vendored
View file

@ -3,6 +3,7 @@ composer.lock
.env
app/logs/*.log
app/cache/*.gz
app/cache/database/.sqlite
TODO.md
node_modules

View file

@ -10,11 +10,11 @@ RUN apt-get update && apt-get install -y \
zip \
git \
htop \
cron \
libzip-dev \
libhiredis-dev \
&& docker-php-ext-install zip opcache \
&& pecl install redis \
&& docker-php-ext-enable redis opcache \
libsqlite3-dev \
&& docker-php-ext-install zip opcache pdo_sqlite \
&& docker-php-ext-enable opcache \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Stage 1: Build stage
@ -46,15 +46,21 @@ COPY default.conf /etc/nginx/sites-available/default
# Copy and configure initialization script permissions
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
COPY bin/cleanup /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh \
&& chmod +x /usr/local/bin/cleanup
# Create cache and logs folders
RUN mkdir -p /app/cache /app/logs
# Create cache, database, and logs folders
RUN mkdir -p /app/cache /app/cache/database /app/logs
# Configure base permissions for /app directory
RUN chown -R www-data:www-data /app \
&& chmod -R 755 /app
# Configure Cron
RUN touch /app/logs/cron.log
RUN echo '0 * * * * root php "/app/bin/cleanup" >> /app/logs/cron.log 2>&1' >> /etc/crontab
EXPOSE 80
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

View file

@ -1,7 +1,7 @@
# 🛠️ Marreta
[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
[![pt-br](https://img.shields.io/badge/lang-pt--br-green.svg)](https://github.com/manualdousuario/marreta/blob/master/README.md)
[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
[![Forks](https://img.shields.io/github/forks/manualdousuario/marreta)](https://github.com/manualdousuario/marreta/network/members)
[![Stars](https://img.shields.io/github/stars/manualdousuario/marreta)](https://github.com/manualdousuario/marreta/stargazers)
@ -9,32 +9,32 @@
Marreta is a tool that breaks access barriers and elements that hinder reading!
![Before and after Marreta](https://github.com/manualdousuario/marreta/blob/main/screen.en.png?raw=true)
![Before and after Marreta](https://github.com/manualdousuario/marreta/blob/main/screen.png?raw=true)
Public instance at [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
## ✨ What's cool about it?
## ✨ What's Cool?
- Cleans and corrects URLs automatically
- Automatically cleans and corrects URLs
- Removes annoying tracking parameters
- Forces HTTPS to keep everything secure
- Changes user agent to avoid blockages
- Leaves the HTML clean and optimized
- Changes user agent to avoid blocking
- Leaves HTML clean and optimized
- Fixes relative URLs on its own
- Allows you to put your own styles and scripts
- Allows you to add your own styles and scripts
- Removes unwanted elements
- Cache, cache!
- Caching, caching!
- Blocks domains you don't want
- Allows you to configure headers and cookies your way
- Allows configuring headers and cookies your way
- PHP-FPM and OPcache
## 🐳 Installing with Docker
Install Docker and Docker Compose
Install [Docker and Docker Compose](https://docs.docker.com/engine/install/)
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml`
Now modify it with your settings:
Now modify with your preferences:
`nano docker-compose.yml`
@ -49,181 +49,36 @@ services:
- SITE_NAME=
- SITE_DESCRIPTION=
- SITE_URL=
- LANGUAGE=
```
- `SITE_NAME`: Your Marreta's name
- `SITE_DESCRIPTION`: What it's for
- `SITE_URL`: Where it will run, complete address with `https://`. If you change the port in docker-compose (e.g. 8080:80), you must also include the port in SITE_URL (e.g. https://yoursite:8080)
- `DNS_SERVERS`: Which DNS servers to use `1.1.1.1, 8.8.8.8`
- `SELENIUM_HOST`: Selenium host server:PORT (e.g. selenium-hub:4444)
- `SITE_NAME`: Name of your Marreta
- `SITE_DESCRIPTION`: Explain what it's for
- `SITE_URL`: Where it will run, full address with `https://`. If you change the port in docker-compose (e.g., 8080:80), you must also include the port in SITE_URL (e.g., https://yoursite:8080)
- `SELENIUM_HOST`: Server:PORT of Selenium host (e.g., selenium-hub:4444)
- `LANGUAGE`: pt-br (Brazilian Portuguese), en (English), es (Spanish), de-de (German), ru-ru (Russian)
Now just run `docker compose up -d`
Now you can run `docker compose up -d`
### S3 Cache
Support for cache storage in S3. Configure the following variables in your `.env`:
```env
S3_CACHE_ENABLED=true
S3_ACCESS_KEY=access_key
S3_SECRET_KEY=secret_key
S3_BUCKET=bucket_name
S3_REGION=us-east-1
S3_FOLDER_=cache/
S3_ACL=private
S3_ENDPOINT=
```
Possible configurations:
```
## R2
S3_ACCESS_KEY=access_key
S3_SECRET_KEY=secret_key
S3_BUCKET=bucket_name
S3_ENDPOINT=https://{TOKEN}.r2.cloudflarestorage.com
S3_REGION=auto
S3_FOLDER_=cache/
S3_ACL=private
## DigitalOcean
S3_ACCESS_KEY=access_key
S3_SECRET_KEY=secret_key
S3_BUCKET=bucket_name
S3_ENDPOINT=https://{REGION}.digitaloceanspaces.com
S3_REGION=auto
S3_FOLDER_=cache/
S3_ACL=private
```
### Selenium Integration
Integration with Selenium allows processing sites that require JavaScript or have some more advanced protection barriers. To use this feature, you need to set up a Selenium environment with Firefox. Add the following configuration to your `docker-compose.yml`:
```yaml
services:
selenium-firefox:
container_name: selenium-firefox
image: selenium/node-firefox:4.27.0-20241204
shm_size: 2gb
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_ENABLE_TRACING=false
- SE_NODE_MAX_SESSIONS=10
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
depends_on:
- selenium-hub
selenium-hub:
image: selenium/hub:4.27.0-20241204
container_name: selenium-hub
environment:
- SE_ENABLE_TRACING=false
- GRID_MAX_SESSION=10
- GRID_BROWSER_TIMEOUT=10
- GRID_TIMEOUT=10
ports:
- 4442:4442
- 4443:4443
- 4444:4444
```
Important settings:
- `shm_size`: Sets the shared memory size for Firefox (2GB recommended)
- `SE_NODE_MAX_SESSIONS`: Maximum number of concurrent sessions per node
- `GRID_MAX_SESSION`: Maximum number of concurrent sessions on the hub
- `GRID_BROWSER_TIMEOUT` and `GRID_TIMEOUT`: Timeouts in seconds
After configuring Selenium, make sure to set the `SELENIUM_HOST` variable in your environment to point to the Selenium hub (usually `selenium-hub:4444`).
## Development
1. First, clone the project:
```bash
git clone https://github.com/manualdousuario/marreta/
cd marreta/app
```
2. Install the project dependencies:
```bash
composer install
npm install
```
3. Create the configuration file:
```bash
cp .env.sample .env
```
4. Configure the environment variables in `.env`
5. Use the `default.conf` as a base for NGINX or point your webservice to `app/`
Gulp is used to compile Sass to CSS, minify JavaScript, use: `gulp`
### ⚙️ Customizing
The settings are organized in `data/`:
- `domain_rules.php`: Specific rules for each site
- `global_rules.php`: Rules that apply to all sites
- `blocked_domains.php`: List of blocked sites
### Translations
- `/languages/`: Each language is in its ISO id (`pt-br, en, es or de-de`) and can be defined in the environment `LANGUAGE`
## 🛠️ Maintenance
### Logging System
Logs are stored in `app/logs/*.log` with automatic rotation every 7 days.
Log settings available in `.env` or docker:
```env
LOG_LEVEL=WARNING
```
Available log levels:
- DEBUG: Detailed information for debugging
- INFO: General information about operations
- WARNING: Warnings that deserve attention (default)
- ERROR: Errors that do not interrupt operation
- CRITICAL: Critical errors that need immediate attention
View the application logs:
```bash
docker-compose logs app
# or directly from the log file
cat app/logs/*.log
```
### Clearing the cache
When you need to clear:
```bash
docker-compose exec app rm -rf /app/cache/*
```
### More configurations:
- Selenium: https://github.com/manualdousuario/marreta/wiki/%F0%9F%92%BB-Selenium-Hub-(Chrome-and-Firefox)
- S3 Cache: https://github.com/manualdousuario/marreta/wiki/%F0%9F%97%83%EF%B8%8F-Cache-S3
- Maintenance: https://github.com/manualdousuario/marreta/wiki/%F0%9F%9B%A0%EF%B8%8F-Maintenance
## 🚀 Integrations
- 🤖 **Telegram**: [Official Bot](https://t.me/leissoai_bot)
- 🦊 **Firefox**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://addons.mozilla.org/pt-BR/firefox/addon/marreta/) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
- 🦊 **Firefox**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://addons.mozilla.org/en-US/firefox/addon/marreta/) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
- 🌀 **Chrome**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://chromewebstore.google.com/detail/marreta/ipelapagohjgjcgpncpbmaaacemafppe) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
- 🦋 **Bluesky**: Bot by [Joselito](https://bsky.app/profile/joseli.to) - [Profile](https://bsky.app/profile/marreta.pcdomanual.com) | [Source Code](https://github.com/manualdousuario/marreta-bot)
- 🍎 **Apple**: Integration with [Shortcuts](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f)
---
Made with ❤️! If you have any questions or suggestions, open an issue and we'll help! 😉
Made with ❤️! If you have questions or suggestions, open an issue and we'll help! 😉
Thanks to the [https://github.com/burlesco/burlesco](Burlesco) and [https://github.com/nang-dev/hover-paywalls-browser-extension/](Hover) projects that served as the basis for several rules!
Special thanks to the projects [Burlesco](https://github.com/burlesco/burlesco) and [Hover](https://github.com/nang-dev/hover-paywalls-browser-extension/) which served as the basis for many rules!
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=manualdousuario/marreta&type=Date)](https://star-history.com/#manualdousuario/marreta&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=manualdousuario/marreta&type=Date)](https://star-history.com/#manualdousuario/marreta&Date)

167
README.md
View file

@ -1,7 +1,7 @@
# 🛠️ Marreta
[![pt-br](https://img.shields.io/badge/lang-pt--br-green.svg)](https://github.com/manualdousuario/marreta/blob/master/README.md)
[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
[![pt-br](https://img.shields.io/badge/lang-pt--br-green.svg)](https://github.com/manualdousuario/marreta/blob/master/README.md)
[![Forks](https://img.shields.io/github/forks/manualdousuario/marreta)](https://github.com/manualdousuario/marreta/network/members)
[![Stars](https://img.shields.io/github/stars/manualdousuario/marreta)](https://github.com/manualdousuario/marreta/stargazers)
@ -30,11 +30,11 @@ Instancia publica em [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
## 🐳 Instalando em Docker
Instale Docker e Docker Compose
Instale [Docker e Docker Compose](https://docs.docker.com/engine/install/)
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml`
Agora modifique com suas configurações:
Agora modifique com suas preferencias:
`nano docker-compose.yml`
@ -49,166 +49,21 @@ services:
- SITE_NAME=
- SITE_DESCRIPTION=
- SITE_URL=
- LANGUAGE=
```
- `SITE_NAME`: Nome do seu Marreta
- `SITE_DESCRIPTION`: Conta pra que serve
- `SITE_URL`: Onde vai rodar, endereço completo com `https://`. Se você alterar a porta no docker-compose (ex: 8080:80), você também deve incluir a porta no SITE_URL (ex: https://seusite:8080)
- `DNS_SERVERS`: Quais servidores DNS usar `1.1.1.1, 8.8.8.8`
- `SELENIUM_HOST`: Servidor:PORTA do host do Selenium (ex: selenium-hub:4444)
-
Agora pode rodar `docker compose up -d`
- `LANGUAGE`: pt-br (Português Brasil), en (Inglês), es (Espanhol) ou de-de (Alemão), ru-ru (Russo)
Agora só rodar `docker compose up -d`
### Cache S3
Suporte de armazenamento do cache em S3. Configure as seguintes variáveis no seu `.env`:
```env
S3_CACHE_ENABLED=true
S3_ACCESS_KEY=access_key
S3_SECRET_KEY=secret_key
S3_BUCKET=nome_do_bucket
S3_REGION=us-east-1
S3_FOLDER_=cache/
S3_ACL=private
S3_ENDPOINT=
```
Configurações possiveis:
```
## R2
S3_ACCESS_KEY=access_key
S3_SECRET_KEY=secret_key
S3_BUCKET=nome_do_bucket
S3_ENDPOINT=https://{TOKEN}.r2.cloudflarestorage.com
S3_REGION=auto
S3_FOLDER_=cache/
S3_ACL=private
## DigitalOcean
S3_ACCESS_KEY=access_key
S3_SECRET_KEY=secret_key
S3_BUCKET=nome_do_bucket
S3_ENDPOINT=https://{REGIAO}.digitaloceanspaces.com
S3_REGION=auto
S3_FOLDER_=cache/
S3_ACL=private
```
### Integração com Selenium
Integração com Selenium permite processar sites que requerem javascript ou têm algumas barreiras de proteção mais avançadas. Para usar esta funcionalidade, você precisa configurar um ambiente Selenium com Firefox. Adicione a seguinte configuração ao seu `docker-compose.yml`:
```yaml
services:
selenium-firefox:
container_name: selenium-firefox
image: selenium/node-firefox:4.27.0-20241204
shm_size: 2gb
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_ENABLE_TRACING=false
- SE_NODE_MAX_SESSIONS=10
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
depends_on:
- selenium-hub
selenium-hub:
image: selenium/hub:4.27.0-20241204
container_name: selenium-hub
environment:
- SE_ENABLE_TRACING=false
- GRID_MAX_SESSION=10
- GRID_BROWSER_TIMEOUT=10
- GRID_TIMEOUT=10
ports:
- 4442:4442
- 4443:4443
- 4444:4444
```
Configurações importantes:
- `shm_size`: Define o tamanho da memória compartilhada para o Firefox (2GB recomendado)
- `SE_NODE_MAX_SESSIONS`: Número máximo de sessões simultâneas por nó
- `GRID_MAX_SESSION`: Número máximo de sessões simultâneas no hub
- `GRID_BROWSER_TIMEOUT` e `GRID_TIMEOUT`: Timeouts em segundos
Após configurar o Selenium, certifique-se de definir a variável `SELENIUM_HOST` no seu ambiente para apontar para o hub do Selenium (geralmente `selenium-hub:4444`).
## Desenvolvimento
1. Primeiro, clone o projeto:
```bash
git clone https://github.com/manualdousuario/marreta/
cd marreta/app
```
2. Instale as dependências do projeto:
```bash
composer install
npm install
```
3. Cria o arquivo de configuração:
```bash
cp .env.sample .env
```
4. Configure as variáveis de ambiente no `.env`
5. Utilize o `default.conf` como base do NGINX ou aponte seu webservice para `app/`
O Gulp é usado para compilar Sass para CSS, minificar JavaScript, utilize: `gulp`
### ⚙️ Personalizando
As configurações estão organizadas em `data/`:
- `domain_rules.php`: Regras específicas para cada site
- `global_rules.php`: Regras que se aplicam a todos os sites
- `blocked_domains.php`: Lista de sites bloqueados
### Traduções
- `/languages/`: Cada lingua está em seu ISO id (`pt-br, en, es ou de-de`) e pode ser definida no environment `LANGUAGE`
## 🛠️ Manutenção
### Sistema de Logs
Os logs são armazenados em `app/logs/*.log` com rotação automática a cada 7 dias.
Configurações de log disponíveis no `.env` ou docker:
```env
LOG_LEVEL=WARNING
```
Níveis de log disponíveis:
- DEBUG: Informações detalhadas para debug
- INFO: Informações gerais sobre operações
- WARNING: Avisos que merecem atenção (padrão)
- ERROR: Erros que não interrompem a operação
- CRITICAL: Erros críticos que precisam de atenção imediata
Ver os logs da aplicação:
```bash
docker-compose logs app
# ou diretamente do arquivo de log
cat app/logs/*.log
```
### Limpando o cache
Quando precisar limpar:
```bash
docker-compose exec app rm -rf /app/cache/*
```
### Mais configurações:
- Selenium: https://github.com/manualdousuario/marreta/wiki/%F0%9F%92%BB-Selenium-Hub-(Chrome-and-Firefox)
- Cache S3: https://github.com/manualdousuario/marreta/wiki/%F0%9F%97%83%EF%B8%8F-Cache-S3
- Manutenção: https://github.com/manualdousuario/marreta/wiki/%F0%9F%9B%A0%EF%B8%8F-Maintenance
## 🚀 Integrações

View file

@ -40,5 +40,10 @@ S3_ENDPOINT=
# Selenium Configuration
SELENIUM_HOST=localhost:4444
# Debug Settings
DEBUG=false
# Debug Settings
DEBUG=false
# Cache Cleanup Settings
# Number of days to keep cache files (*.gz)
# If not set, no files will be cleaned
CLEANUP_DAYS=7

View file

@ -85,4 +85,20 @@ document.addEventListener('DOMContentLoaded', function () {
}
}
});
// Paste button functionality
const pasteButton = document.getElementById('paste');
const urlInput = document.getElementById('url');
if (pasteButton && urlInput) {
pasteButton.addEventListener('click', async (e) => {
e.preventDefault();
try {
const clipboardText = await navigator.clipboard.readText();
urlInput.value = clipboardText.trim();
} catch (err) {
console.error('Failed to read clipboard contents', err);
}
});
}
});

View file

@ -38,4 +38,6 @@
@include mixin.icon('warning', 'invert(89%) sepia(25%) saturate(5861%) hue-rotate(353deg) brightness(101%) contrast(101%)');
@include mixin.icon('hamburguer', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
@include mixin.icon('close', 'invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%)');
@include mixin.icon('close', 'invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%)');
@include mixin.icon('paste', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');

View file

@ -496,6 +496,23 @@ main {
}
}
.paste {
background: rgb(244,244,245);
background: linear-gradient(90deg, rgba(244,244,245,0) 0%, rgba(244,244,245,1) 30%, rgba(244,244,245,1) 100%);
align-items: center;
z-index: 3;
position: absolute;
top: 4px;
padding: 0 18px 0 22px;
right: 50px;
cursor: pointer;
height: 48px;
display: flex;
.icon {
}
}
button {
position: relative;
background-color: var(--marreta);

0
app/cache/database/.gitkeep vendored Normal file
View file

View file

@ -5,7 +5,8 @@
"php-curl-class/php-curl-class": "^11.0",
"php-webdriver/webdriver": "^1.15",
"monolog/monolog": "^3.8.1",
"nikic/fast-route": "^1.3"
"nikic/fast-route": "^1.3",
"league/climate": "^3.8"
},
"autoload": {
"psr-4": {

View file

@ -30,6 +30,7 @@ try {
define('SITE_NAME', $_ENV['SITE_NAME']);
define('SITE_DESCRIPTION', $_ENV['SITE_DESCRIPTION']);
define('SITE_URL', $_ENV['SITE_URL']);
define('CLEANUP_DAYS', $_ENV['CLEANUP_DAYS'] ?? 0);
// Optional settings with defaults
define('DNS_SERVERS', $_ENV['DNS_SERVERS'] ?? '1.1.1.1, 8.8.8.8');
@ -38,11 +39,6 @@ try {
define('CACHE_DIR', __DIR__ . '/cache');
define('LANGUAGE', $_ENV['LANGUAGE'] ?? 'pt-br');
// Redis connection settings
define('REDIS_HOST', $_ENV['REDIS_HOST'] ?? 'localhost');
define('REDIS_PORT', $_ENV['REDIS_PORT'] ?? 6379);
define('REDIS_PREFIX', $_ENV['REDIS_PREFIX'] ?? 'marreta:');
// Logging configuration
define('LOG_LEVEL', $_ENV['LOG_LEVEL'] ?? 'WARNING'); // DEBUG, INFO, WARNING, ERROR, CRITICAL
define('LOG_DAYS_TO_KEEP', 7);

View file

@ -27,6 +27,7 @@ return [
'mittelbayerische.de',
'josimarfootball.com',
'nordsee-zeitung.de',
'zorgvisie.nl',
// List of common blocked sites to avoid unnecessary requests
//-- Technical access blocking
'bloomberg.com',

View file

@ -40,6 +40,12 @@ return [
'removeElementsByTag' => ['style'],
'removeCustomAttr' => ['hidden','data-*']
],
'wired.com' => [
'scriptTagRemove' => ['.js'],
],
'newyorker.com' => [
'scriptTagRemove' => ['.js'],
],
'globo.com' => [
'idElementRemove' => ['cookie-banner-lgpd', 'paywall-cpt', 'mc-read-more-wrapper', 'paywall-cookie-content', 'paywall-cpt'],
'classElementRemove' => ['banner-lgpd', 'article-related-link__title', 'article-related-link__picture', 'paywall-denied', 'banner-subscription'],
@ -64,6 +70,18 @@ return [
'classElementRemove' => ['leaderboard__container'],
'fetchStrategies' => 'fetchFromSelenium',
],
'lepoint.fr' => [
'classElementRemove' => ['paywall'],
],
'gamestar.de' => [
'classElementRemove' => ['plus-teaser'],
'classAttrRemove' => ['plus-'],
'idElementRemove' => ['commentReload']
],
'heise.de' => [
'classAttrRemove' => ['curtain__purchase-container'],
'removeElementsByTag' => ['a-gift']
],
'fortune.com' => [
'classElementRemove' => ['latest-popular-module','own','drawer-menu'],
'fetchStrategies' => 'fetchFromSelenium',
@ -130,6 +148,21 @@ return [
'paywall_access' => 'true'
]
],
'ftm.nl' => [
'fetchStrategies' => 'fetchFromSelenium',
'removeCustomAttr' => ['dialog','iframe'],
'classElementRemove' => ['modal'],
'scriptTagRemove' => ['footer.min','diffuser.js','insight.ftm.nl'],
'classAttrRemove' => ['hasBlockingOverlay', 'localstorage']
],
'denikn.cz' => [
'idElementRemove' => ['e_lock__hard']
],
'dtest.cz' => [
'fetchStrategies' => 'fetchFromSelenium',
'classAttrRemove' => ['is-hidden-compare'],
'classElementRemove' => ['cc-window']
],
'uol.com.br' => [
'scriptTagRemove' => ['me.jsuol.com.br', 'c.jsuol.com.br'],
'classElementRemove' => ['header-top-wrapper'],
@ -152,7 +185,8 @@ return [
]
],
'theverge.com' => [
'fetchStrategies' => 'fetchFromSelenium',
'scriptTagRemove' => 'zephr',
'classElementRemove' => 'zephr'
],
'economist.com' => [
'cookies' => [
@ -179,7 +213,7 @@ return [
'fromGoogleBot' => true
],
'nytimes.com' => [
'idElementRemove' => ['gateway-content','site-index'],
'idElementRemove' => ['gateway-content','site-index','complianceOverlay'],
'customCode' => '
setTimeout(function() {
const walk = document.createTreeWalker(

View file

@ -68,6 +68,8 @@ return [
'getblue.io',
'smartocto.com',
'cdn.pn.vg',
'static.vocstatic.com'
'static.vocstatic.com',
'recaptcha',
'intercom'
]
];

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
app/dist/icons/paste.svg vendored Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33333 2.66667H3.45333C3.72667 3.44 4.46 4 5.33333 4H6.66667C7.53333 4 8.26667 3.44 8.54667 2.66667H8.66667C9.58 2.66667 10.3733 3.28 10.6067 4.16667C10.7 4.52667 11.06 4.74 11.42 4.64667C11.7733 4.55333 11.9933 4.19333 11.9 3.83333C11.52 2.36 10.1933 1.33333 8.67333 1.33333H8.56C8.28667 0.56 7.54667 0 6.67333 0H5.34C4.47333 0 3.72667 0.56 3.45333 1.33333H3.34C1.49333 1.33333 0 2.82667 0 4.66667V12.6667C0 14.5067 1.49333 16 3.33333 16H5.33333C5.7 16 6 15.7 6 15.3333C6 14.9667 5.7 14.6667 5.33333 14.6667H3.33333C2.23333 14.6667 1.33333 13.7667 1.33333 12.6667V4.66667C1.33333 3.56667 2.23333 2.66667 3.33333 2.66667ZM5.33333 1.33333H6.66667C7.03333 1.33333 7.33333 1.63333 7.33333 2C7.33333 2.36667 7.03333 2.66667 6.66667 2.66667H5.33333C4.96667 2.66667 4.66667 2.36667 4.66667 2C4.66667 1.63333 4.96667 1.33333 5.33333 1.33333ZM13.3333 6H10C8.52667 6 7.33333 7.19333 7.33333 8.66667V13.3333C7.33333 14.8067 8.52667 16 10 16H13.3333C14.8067 16 16 14.8067 16 13.3333V8.66667C16 7.19333 14.8067 6 13.3333 6ZM14.6667 13.3333C14.6667 14.0667 14.0667 14.6667 13.3333 14.6667H10C9.26667 14.6667 8.66667 14.0667 8.66667 13.3333V8.66667C8.66667 7.93333 9.26667 7.33333 10 7.33333H13.3333C14.0667 7.33333 14.6667 7.93333 14.6667 8.66667V13.3333ZM13.3333 9.33333C13.3333 9.7 13.0333 10 12.6667 10H10.6667C10.3 10 10 9.7 10 9.33333C10 8.96667 10.3 8.66667 10.6667 8.66667H12.6667C13.0333 8.66667 13.3333 8.96667 13.3333 9.33333ZM13.3333 12C13.3333 12.3667 13.0333 12.6667 12.6667 12.6667H10.6667C10.3 12.6667 10 12.3667 10 12C10 11.6333 10.3 11.3333 10.6667 11.3333H12.6667C13.0333 11.3333 13.3333 11.6333 13.3333 12Z" fill="black"/>
</svg>

After

(image error) Size: 1.7 KiB

View file

@ -1,2 +1,2 @@
"serviceWorker"in navigator&&window.addEventListener("load",()=>{navigator.serviceWorker.register("/service-worker.js").then(()=>{}).catch(()=>{})}),document.addEventListener("DOMContentLoaded",function(){let t=document.querySelector(".integration");var e=document.querySelector(".integration__toggle");let o=document.querySelector(".extension");var n=document.querySelector(".extension__toggle");let s=e=>{e!==t&&t.classList.remove("open"),e!==o&&o.classList.remove("open")};e.addEventListener("click",e=>{e.stopPropagation(),s(t),t.classList.toggle("open")}),n.addEventListener("click",e=>{e.stopPropagation(),s(o),o.classList.toggle("open")}),t.addEventListener("click",e=>{e.stopPropagation()}),o.addEventListener("click",e=>{e.stopPropagation()}),document.addEventListener("click",()=>{t.classList.remove("open"),o.classList.remove("open")}),document.addEventListener("click",e=>{e=e.target.closest(".toasty");e&&e.remove()}),document.addEventListener("click",e=>{e.target.closest(".open-nav")&&((e=document.querySelector("header")).classList.contains("open")?e.classList.remove("open"):e.classList.add("open"))})});
"serviceWorker"in navigator&&window.addEventListener("load",()=>{navigator.serviceWorker.register("/service-worker.js").then(()=>{}).catch(()=>{})}),document.addEventListener("DOMContentLoaded",function(){let t=document.querySelector(".integration");var e=document.querySelector(".integration__toggle");let o=document.querySelector(".extension");var n=document.querySelector(".extension__toggle");let r=e=>{e!==t&&t.classList.remove("open"),e!==o&&o.classList.remove("open")};e.addEventListener("click",e=>{e.stopPropagation(),r(t),t.classList.toggle("open")}),n.addEventListener("click",e=>{e.stopPropagation(),r(o),o.classList.toggle("open")}),t.addEventListener("click",e=>{e.stopPropagation()}),o.addEventListener("click",e=>{e.stopPropagation()}),document.addEventListener("click",()=>{t.classList.remove("open"),o.classList.remove("open")}),document.addEventListener("click",e=>{e=e.target.closest(".toasty");e&&e.remove()}),document.addEventListener("click",e=>{e.target.closest(".open-nav")&&((e=document.querySelector("header")).classList.contains("open")?e.classList.remove("open"):e.classList.add("open"))});e=document.getElementById("paste");let a=document.getElementById("url");e&&a&&e.addEventListener("click",async e=>{e.preventDefault();try{var t=await navigator.clipboard.readText();a.value=t.trim()}catch(e){console.error("Failed to read clipboard contents",e)}})});
//# sourceMappingURL=scripts.js.map

File diff suppressed because one or more lines are too long

View file

@ -5,7 +5,7 @@ namespace Inc;
use Inc\Cache\CacheStorageInterface;
use Inc\Cache\DiskStorage;
use Inc\Cache\S3Storage;
use Inc\Cache\RedisStorage;
use Inc\Cache\SQLiteStorage;
/**
* System cache management with multiple storage backends (disk/S3)
@ -17,18 +17,18 @@ class Cache
/** @var CacheStorageInterface Cache storage implementation */
private $storage;
/** @var RedisStorage Redis instance for file counting */
private $redisStorage;
/** @var SQLiteStorage SQLite instance for file counting */
private $sqliteStorage;
/**
* Initializes storage based on configuration
* Uses S3Storage if configured and enabled
* Defaults to DiskStorage otherwise
* Defaults to SQLiteStorage otherwise (which delegates to DiskStorage)
*/
public function __construct()
{
$this->redisStorage = new RedisStorage(CACHE_DIR);
$this->sqliteStorage = new SQLiteStorage(CACHE_DIR);
if (defined('S3_CACHE_ENABLED') && S3_CACHE_ENABLED === true) {
$this->storage = new S3Storage([
'key' => S3_ACCESS_KEY,
@ -40,14 +40,14 @@ class Cache
'endpoint' => defined('S3_ENDPOINT') ? S3_ENDPOINT : null
]);
} else {
$this->storage = new DiskStorage(CACHE_DIR);
$this->storage = $this->sqliteStorage;
}
}
/** Gets total number of cached files */
public function getCacheFileCount(): int
{
return $this->redisStorage->countCacheFiles();
return $this->sqliteStorage->countCacheFiles();
}
/**

View file

@ -1,128 +0,0 @@
<?php
namespace Inc\Cache;
use Redis;
/**
* Redis-based cache storage implementation
* Provides cache storage and file counting functionality using Redis
*/
class RedisStorage implements CacheStorageInterface
{
/**
* @var \Redis|null Redis client instance
*/
private $redis;
/**
* @var string Cache directory for file counting
*/
private $cacheDir;
/**
* Class constructor
* @param string $cacheDir Base directory for cache storage
*/
public function __construct(string $cacheDir)
{
$this->cacheDir = $cacheDir;
// Try to initialize Redis connection
try {
$this->redis = new \Redis();
$this->redis->connect(REDIS_HOST, REDIS_PORT, 2.5);
$this->redis->setOption(\Redis::OPT_PREFIX, REDIS_PREFIX);
} catch (\Exception $e) {
$this->redis = null;
}
}
/**
* Counts the number of files in the cache directory
* @return int Number of files in the cache directory
*/
public function countCacheFiles(): int
{
$cacheCountKey = 'cache_file_count';
if ($this->redis !== null) {
$cachedCount = $this->redis->get($cacheCountKey);
if ($cachedCount !== false) {
return (int)$cachedCount;
}
}
$fileCount = 0;
$iterator = new \FilesystemIterator($this->cacheDir);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'gz') {
$fileCount++;
}
}
if ($this->redis !== null) {
$this->redis->set($cacheCountKey, $fileCount);
}
return $fileCount;
}
/**
* Updates the file count in Redis
* @param int $count Number of files
*/
public function updateCacheFileCount(int $count): void
{
if ($this->redis !== null) {
$this->redis->set('cache_file_count', $count);
}
}
/**
* Checks if cache exists for a given ID
* @param string $id Cache ID
* @return bool True if cache exists, false otherwise
*/
public function exists(string $id): bool
{
return $this->redis !== null ? $this->redis->exists($id) : false;
}
/**
* Retrieves cached content
* @param string $id Cache ID
* @return string|null Cached content or null if not found
*/
public function get(string $id): ?string
{
if ($this->redis === null) {
return null;
}
$content = $this->redis->get($id);
return $content === false ? null : $content;
}
/**
* Stores content in cache
* @param string $id Cache ID
* @param string $content Content to be stored
* @return bool True if successful, false otherwise
*/
public function set(string $id, string $content): bool
{
if ($this->redis === null) {
return false;
}
$result = $this->redis->set($id, $content);
if ($result) {
$currentCount = $this->redis->get('cache_file_count') ?: 0;
$this->redis->set('cache_file_count', $currentCount + 1);
}
return $result;
}
}

View file

@ -0,0 +1,177 @@
<?php
namespace Inc\Cache;
use PDO;
use PDOException;
/**
* SQLite-based cache storage implementation
* Provides file counting functionality using SQLite
* Delegates actual cache storage to DiskStorage
*/
class SQLiteStorage implements CacheStorageInterface
{
/**
* @var PDO|null SQLite connection
*/
private $db;
/**
* @var string Cache directory for file counting
*/
private $cacheDir;
/**
* @var string Path to SQLite database file
*/
private $dbPath;
/**
* @var DiskStorage Disk storage for cache entries
*/
private $diskStorage;
/**
* Class constructor
* @param string $cacheDir Base directory for cache storage
*/
public function __construct(string $cacheDir)
{
$this->cacheDir = $cacheDir;
$this->diskStorage = new DiskStorage($cacheDir);
// Ensure database directory exists
$dbDir = $cacheDir . '/database';
if (!is_dir($dbDir)) {
mkdir($dbDir, 0755, true);
}
$this->dbPath = $dbDir . '/.sqlite';
// Try to initialize SQLite connection
try {
$this->db = new PDO('sqlite:' . $this->dbPath);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Create tables if they don't exist
$this->initDatabase();
// If database file was just created, count cache files
if (!file_exists($this->dbPath) || filesize($this->dbPath) < 1024) {
$this->countCacheFiles();
}
} catch (PDOException $e) {
$this->db = null;
}
}
/**
* Initialize database tables
*/
private function initDatabase(): void
{
$this->db->exec("
CREATE TABLE IF NOT EXISTS stats (
key TEXT PRIMARY KEY,
value INTEGER NOT NULL
)
");
}
/**
* Counts the number of files in the cache directory
* @return int Number of files in the cache directory
*/
public function countCacheFiles(): int
{
if ($this->db !== null) {
try {
$stmt = $this->db->query("SELECT value FROM stats WHERE key = 'count'");
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
return (int)$result['value'];
}
} catch (PDOException $e) {
// Continue to count files if query fails
}
}
$fileCount = 0;
$iterator = new \FilesystemIterator($this->cacheDir);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'gz') {
$fileCount++;
}
}
if ($this->db !== null) {
$this->updateCacheFileCount($fileCount);
}
return $fileCount;
}
/**
* Updates the file count in SQLite
* @param int $count Number of files
*/
public function updateCacheFileCount(int $count): void
{
if ($this->db !== null) {
try {
$stmt = $this->db->prepare("
INSERT OR REPLACE INTO stats (key, value)
VALUES ('count', :count)
");
$stmt->bindParam(':count', $count, PDO::PARAM_INT);
$stmt->execute();
} catch (PDOException $e) {
// Silently fail if update fails
}
}
}
/**
* Checks if cache exists for a given ID
* Delegates to DiskStorage
* @param string $id Cache ID
* @return bool True if cache exists, false otherwise
*/
public function exists(string $id): bool
{
return $this->diskStorage->exists($id);
}
/**
* Retrieves cached content
* Delegates to DiskStorage
* @param string $id Cache ID
* @return string|null Cached content or null if not found
*/
public function get(string $id): ?string
{
return $this->diskStorage->get($id);
}
/**
* Stores content in cache
* Delegates to DiskStorage and updates file count
* @param string $id Cache ID
* @param string $content Content to be stored
* @return bool True if successful, false otherwise
*/
public function set(string $id, string $content): bool
{
$result = $this->diskStorage->set($id, $content);
if ($result) {
// Increment cache file count
$currentCount = $this->countCacheFiles();
$this->updateCacheFileCount($currentCount + 1);
}
return $result;
}
}

View file

@ -237,11 +237,11 @@ class URLAnalyzerProcess extends URLAnalyzerBase
$body = $xpath->query('//body')->item(0);
if ($body) {
$brandDiv = $dom->createElement('div');
$brandDiv->setAttribute('style', 'z-index: 99999; position: fixed; top: 0; right: 1rem; display: flex; gap: 8px;');
$brandDiv->setAttribute('style', 'z-index: 9999; position: fixed; top: 0; right: 1rem; display: flex; gap: 8px;');
$brandHtml = $dom->createDocumentFragment();
$brandHtml->appendXML('<a href="' . htmlspecialchars($url) . '" style="color: #fff; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 16 16" width="20" height="20"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg></a>');
$brandHtml->appendXML('<a href="' . htmlspecialchars($url) . '" style="color: #fff; line-height: 1em; z-index: 9999; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 16 16" width="20" height="20"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg></a>');
$brandDiv->appendChild($brandHtml);
$brandHtml->appendXML('<a href="' . htmlspecialchars(SITE_URL) . '" style="color: #fff; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 640 512" width="20" height="20"><path d="m283.9 378.6 18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9-85.7-42.9-104.3-52.2c-15.8-7.9-22.2-27.1-14.3-42.9l40-80C48.8 22.8 59.9 16 72 16h120c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zm-204.6-39.5 85.9 42.9L90.9 485.5C79 509.2 50.2 518.8 26.5 507s-33.3-40.8-21.4-64.5l102.5-205.1z"/></svg></a>');
$brandHtml->appendXML('<a href="' . htmlspecialchars(SITE_URL) . '" style="color: #fff; line-height: 1em; z-index: 9999; text-decoration: none; font-weight: bold; background: rgba(37,99,235, 0.9); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 6px 10px; margin: 0px; overflow: hidden; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 640 512" width="20" height="20"><path d="m283.9 378.6 18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9-85.7-42.9-104.3-52.2c-15.8-7.9-22.2-27.1-14.3-42.9l40-80C48.8 22.8 59.9 16 72 16h120c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zm-204.6-39.5 85.9 42.9L90.9 485.5C79 509.2 50.2 518.8 26.5 507s-33.3-40.8-21.4-64.5l102.5-205.1z"/></svg></a>');
$brandDiv->appendChild($brandHtml);
$body->appendChild($brandDiv);
}
@ -254,7 +254,7 @@ class URLAnalyzerProcess extends URLAnalyzerBase
$body = $xpath->query('//body')->item(0);
if ($body) {
$debugDiv = $dom->createElement('div');
$debugDiv->setAttribute('style', 'position: fixed; bottom: 1rem; right: 1rem; max-width: 400px; padding: 1rem; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); border: 1px solid #e5e7eb; border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: auto; max-height: 80vh; z-index: 9999; font-family: monospace; font-size: 13px; line-height: 1.4;');
$debugDiv->setAttribute('style', 'position: fixed; bottom: 1rem; right: 1rem; max-width: 400px; padding: 1rem; color: #000; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(8px); border: 1px solid #e5e7eb; border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: auto; max-height: 80vh; z-index: 9999; font-family: monospace; font-size: 13px; line-height: 1.4;');
if (empty($this->activatedRules)) {
$ruleElement = $dom->createElement('div');

View file

@ -4,8 +4,8 @@ return [
'walls_destroyed' => 'стены разрушены!',
'url_placeholder' => 'Введите URL (например, https://example.com)',
'analyze_button' => 'Анализировать',
'nav_integration' => 'Integrations',
'nav_extension' => 'Install',
'nav_integration' => 'Интеграции',
'nav_extension' => 'Установить',
'bookmarklet_title' => 'Добавить в закладки',
'bookmarklet_description' => 'Перетащите кнопку ниже на панель закладок, чтобы быстро получить доступ к на любой странице:',
'open_in' => 'Открыть в {site_name}',

View file

@ -86,6 +86,7 @@
required
pattern="https?://.+"
title="<?php echo \Inc\Language::getMessage('INVALID_URL')['message']; ?>">
<span class="paste" id="paste"><span class="icon icon--paste"></span></span>
</div>
<button type="submit" alt="<?php echo \Inc\Language::get('analyze_button'); ?>">
<span class="icon icon--marreta"></span>

211
bin/cleanup Normal file
View file

@ -0,0 +1,211 @@
#!/usr/bin/env php
<?php
/**
* Cache Cleanup Script
*
* Removes *.gz files from the cache directory that are older than the number
* of days specified in the CLEANUP_DAYS environment variable.
* If CLEANUP_DAYS is not set, no files will be cleaned.
*/
require_once __DIR__ . '/../app/vendor/autoload.php';
use League\CLImate\CLImate;
use Dotenv\Dotenv;
use Aws\S3\S3Client;
use Aws\Exception\AwsException;
$climate = new CLImate();
$climate->bold()->out('Cache Cleanup Tool');
$climate->br();
$cleanupDays = 0;
try {
$dotenv = Dotenv::createImmutable(__DIR__ . '/../app');
$dotenv->load();
$climate->out('Environment variables loaded');
$cleanupDays = $_ENV['CLEANUP_DAYS'];
} catch (\Exception $e) {
$climate->yellow()->out('Warning: ' . $e->getMessage());
exit(0);
}
if (!defined('CACHE_DIR')) {
define('CACHE_DIR', __DIR__ . '/../app/cache');
}
if ($cleanupDays == 0) {
$climate->yellow()->out('CLEANUP_DAYS variable not set or 0. No files will be cleaned.');
exit(0);
}
$cleanupDays = (int)$cleanupDays;
if ($cleanupDays <= 0) {
$climate->red()->out('CLEANUP_DAYS must be a positive integer. No files will be cleaned.');
exit(1);
};
// Calculate the cutoff timestamp
$cutoffTime = time() - ($cleanupDays * 86400);
// Check if S3 cache is enabled
$s3CacheEnabled = isset($_ENV['S3_CACHE_ENABLED']) && filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN);
if ($s3CacheEnabled) {
// Clean S3 cache
cleanS3Cache($climate, $cutoffTime, $cleanupDays);
} else {
// Clean local disk cache
cleanDiskCache($climate, $cutoffTime, $cleanupDays);
}
/**
* Clean cache files from S3 bucket
*
* @param CLImate $climate CLImate instance for output
* @param int $cutoffTime Timestamp to use as cutoff for file age
* @param int $cleanupDays Number of days to keep files
*/
function cleanS3Cache($climate, $cutoffTime, $cleanupDays) {
$requiredVars = ['S3_ACCESS_KEY', 'S3_SECRET_KEY', 'S3_BUCKET'];
foreach ($requiredVars as $var) {
if (!isset($_ENV[$var]) || empty($_ENV[$var])) {
$climate->red()->out("$var environment variable is required for S3 cache cleaning.");
exit(1);
}
}
$climate->out("S3 cache enabled. Cleaning S3 cache files older than {$cleanupDays} days...");
$clientConfig = [
'version' => 'latest',
'region' => $_ENV['S3_REGION'] ?? 'us-east-1',
'credentials' => [
'key' => $_ENV['S3_ACCESS_KEY'],
'secret' => $_ENV['S3_SECRET_KEY'],
]
];
if (!empty($_ENV['S3_ENDPOINT'])) {
$clientConfig['endpoint'] = $_ENV['S3_ENDPOINT'];
$clientConfig['use_path_style_endpoint'] = true;
}
try {
$s3Client = new S3Client($clientConfig);
$bucket = $_ENV['S3_BUCKET'];
$prefix = $_ENV['S3_FOLDER'] ?? 'cache/';
$climate->out("Listing objects in bucket: {$bucket} with prefix: {$prefix}");
$objects = [];
$marker = null;
do {
$params = [
'Bucket' => $bucket,
'Prefix' => $prefix,
'MaxKeys' => 1000
];
if ($marker) {
$params['Marker'] = $marker;
}
$result = $s3Client->listObjects($params);
if (isset($result['Contents'])) {
foreach ($result['Contents'] as $object) {
if (substr($object['Key'], -3) === '.gz') {
$objects[] = $object;
}
}
}
$marker = $result['NextMarker'] ?? ($result['IsTruncated'] ? end($result['Contents'])['Key'] : null);
} while ($marker);
$totalObjects = count($objects);
$climate->out("Found {$totalObjects} .gz objects in S3 bucket.");
if ($totalObjects === 0) {
$climate->out('No .gz objects found in S3 bucket.');
return;
}
$progress = $climate->progress()->total($totalObjects);
$deletedObjects = 0;
foreach ($objects as $index => $object) {
$progress->current($index + 1);
$lastModified = strtotime($object['LastModified']);
if ($lastModified < $cutoffTime) {
try {
$s3Client->deleteObject([
'Bucket' => $bucket,
'Key' => $object['Key']
]);
$deletedObjects++;
} catch (AwsException $e) {
$climate->red()->out("Failed to delete: " . $object['Key'] . " - " . $e->getMessage());
}
}
}
$climate->br();
$climate->green()->out("S3 cleanup complete: {$deletedObjects} objects deleted.");
} catch (AwsException $e) {
$climate->red()->out("AWS Error: " . $e->getMessage());
exit(1);
}
}
/**
* Clean cache files from local disk
*
* @param CLImate $climate CLImate instance for output
* @param int $cutoffTime Timestamp to use as cutoff for file age
* @param int $cleanupDays Number of days to keep files
*/
function cleanDiskCache($climate, $cutoffTime, $cleanupDays) {
$cacheDir = CACHE_DIR;
$climate->out("Cleaning cache files older than {$cleanupDays} days from: {$cacheDir}");
if (!is_dir($cacheDir)) {
$climate->red()->out("Cache directory not found: {$cacheDir}");
exit(1);
}
$gzFiles = glob($cacheDir . '/*.gz');
$totalFiles = count($gzFiles);
$deletedFiles = 0;
if ($totalFiles === 0) {
$climate->out('No .gz files found in cache directory.');
return;
}
$climate->out("Found {$totalFiles} .gz files in cache directory.");
$progress = $climate->progress()->total($totalFiles);
foreach ($gzFiles as $index => $file) {
$progress->current($index + 1);
$fileTime = filemtime($file);
if ($fileTime < $cutoffTime) {
if (unlink($file)) {
$deletedFiles++;
} else {
$climate->red()->out("Failed to delete: " . basename($file));
}
}
}
$climate->br();
$climate->green()->out("Disk cleanup complete: {$deletedFiles} files deleted.");
}

View file

@ -31,6 +31,12 @@ server {
}
location ~ \.php$ {
if ($uri ~ ^/p/) {
rewrite ^/p/(.*)$ /index.php?url=$1 last;
}
if ($uri ~ ^/api/) {
rewrite ^/api/(.*)$ /index.php?url=$1 last;
}
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9000;

View file

@ -1,4 +1,5 @@
services:
# Marreta
marreta:
container_name: marreta
image: ghcr.io/manualdousuario/marreta:latest
@ -11,11 +12,27 @@ services:
- SITE_NAME=${SITE_NAME:-}
- SITE_DESCRIPTION=${SITE_DESCRIPTION:-}
- SITE_URL=${SITE_URL:-https://marreta.localhost}
- DNS_SERVERS=${DNS_SERVER:-}
- LANGUAGE=${LANGUAGE:-}
- DNS_SERVERS=${DNS_SERVER:-1.1.1.1,8.8.8.8}
- LANGUAGE=${LANGUAGE:-pt-br}
- LOG_LEVEL=${LOG_LEVEL:-WARNING}
- SELENIUM_HOST=${SELENIUM_HOST:-selenium-hub:4444}
- CLEANUP_DAYS=7 # Optional
restart: unless-stopped
# Selenium
selenium-hub:
image: selenium/hub:4.27.0-20241204
container_name: selenium-hub
environment:
- SE_ENABLE_TRACING=false
- GRID_MAX_SESSION=10
- GRID_BROWSER_TIMEOUT=10
- GRID_TIMEOUT=10
depends_on:
- marreta
ports:
- 4442:4442
- 4443:4443
- 4444:4444
selenium-chromium:
container_name: selenium-chromium
image: selenium/node-chromium:4.27.0-20241204
@ -43,18 +60,4 @@ services:
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
depends_on:
- selenium-hub
selenium-hub:
image: selenium/hub:4.27.0-20241204
container_name: selenium-hub
environment:
- SE_ENABLE_TRACING=false
- GRID_MAX_SESSION=10
- GRID_BROWSER_TIMEOUT=10
- GRID_TIMEOUT=10
depends_on:
- marreta
ports:
- 4442:4442
- 4443:4443
- 4444:4444
- selenium-hub

View file

@ -54,6 +54,7 @@ log_success "Environment variables configured"
log_info "Adjusting directory permissions..."
mkdir -p /app/cache /app/logs # Ensures directories exist
mkdir -p /app/cache/database
chown -R www-data:www-data /app/cache /app/logs
chmod -R 775 /app/cache /app/logs
@ -108,6 +109,11 @@ nginx -g "daemon off;" &
sleep 3
check_nginx
# Starting Cron
log_info "Starting Cron..."
service cron restart
log_success "Cron started"
echo -e "\n${GREEN}=== Marreta initialized ===${NC}\n"
# Wait for any process to exit