Compare commits

...

40 commits
1.19.1 ... 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
Renan Bernordi
cfc13da108 dockerfix 2025-02-06 23:37:51 -03:00
Renan Bernordi
c54b89eb15 dockerfix 2025-02-06 23:30:43 -03:00
Renan Bernordi
545e8a7980 link original na barra 2025-02-06 22:35:53 -03:00
Renan Bernordi
dc297cbff8 versão mobile 2025-02-06 01:37:20 -03:00
Renan Bernordi
ff1e1bcc86 bloqueios no robots 2025-01-30 13:03:15 -03:00
Renan Bernordi
c945798d09 rm 2025-01-30 13:01:56 -03:00
Renan Bernordi
db4e512e63 adicionada documentação na nova estrutura do urlanalyzer 2025-01-30 01:45:29 -03:00
Renan Bernordi
91f58e61c7 extends urlanalyzer 2025-01-30 01:35:01 -03:00
Renan Bernordi
9ffd8260fd revisão no docker 2025-01-29 23:40:28 -03:00
Renan Bernordi
d8568c06e9 revisão dos readmes 2025-01-29 21:23:41 -03:00
Renan Bernordi
3875b19817 ajustes de tradução e documentação 2025-01-29 21:02:39 -03:00
Renan Bernordi
c41ca87e4e ajustes no readme 2025-01-29 21:02:20 -03:00
Renan Bernordi
1f5fb428a3 simplificação de documentação 2025-01-29 20:46:05 -03:00
Renan Bernordi
d9ef063243 ajuste de traduções 2025-01-29 20:44:46 -03:00
Renan Bernordi
84291c7739 limpeza de codigos 2025-01-29 19:49:32 -03:00
Renan Bernordi
90bcbd97fd desk 2025-01-28 00:10:23 -03:00
Renan Bernordi
1e205b6b2e ajustes gulp 2025-01-27 15:09:16 -03:00
Renan Bernordi
88b37a5325 inicio da v2 2025-01-27 14:50:00 -03:00
Renan Bernordi
99ca0f420f ajuste para pwa 2025-01-26 20:53:38 -03:00
Renan Bernordi
f8f30b2c4d ajuste no tratamento da home 2025-01-26 19:46:14 -03:00
Renan Bernordi
9a3753e80a ajuste visuais nas barras 2025-01-24 18:58:09 -03:00
Renan Bernordi
5d2363ba3a correção de header 2025-01-24 18:58:00 -03:00
127 changed files with 16244 additions and 2357 deletions

View file

@ -1,5 +1,5 @@
name: 🛠️ Main
run-name: 🚀 Deploy de versão
run-name: 🚀 Version Deployment
on:
push:
@ -9,49 +9,58 @@ on:
env:
DOCKER_REGISTRY: ghcr.io
DOCKER_IMAGE_NAME: ${{ github.repository }}
DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_USERNAME }}/marreta
jobs:
docker-build:
name: 🐳 Build e Push
name: 🐳 Build and Push
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: 📥 Checkout código
- name: 📥 Checkout code
uses: actions/checkout@v4
- name: 🏷️ Extrair versão da tag
- name: 🏷️ Extract version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: 🔧 Configurar QEMU
- name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v3
- name: 🛠️ Configurar Docker Buildx
- name: 🛠️ Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: 📋 Extrair metadata Docker
- name: 📋 Extract Docker metadata
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: 🔐 Login no 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: 🏗️ Build e Push
- 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:
context: .
@ -63,21 +72,21 @@ jobs:
cache-to: type=gha,mode=max
publish-release:
name: 📦 Publicar Release
name: 📦 Publish Release
runs-on: ubuntu-latest
needs: docker-build
permissions:
contents: write
steps:
- name: 📥 Checkout código
- name: 📥 Checkout code
uses: actions/checkout@v4
- name: 🏷️ Extrair versão da tag
- name: 🏷️ Extract version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: 📝 Criar Release
- name: 📝 Create Release
uses: softprops/action-gh-release@v1
with:
name: "🎉 Release v${{ steps.get_version.outputs.VERSION }}"

52
.gitignore vendored
View file

@ -3,48 +3,22 @@ composer.lock
.env
app/logs/*.log
app/cache/*.gz
app/cache/database/.sqlite
TODO.md
node_modules
# Created by https://www.toptal.com/developers/gitignore/api/composer,windows,macos,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=composer,windows,macos,linux
### Composer ###
composer.phar
/vendor/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
@ -52,42 +26,22 @@ Icon
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/composer,windows,macos,linux
*.lnk

View file

@ -1,50 +1,66 @@
FROM php:8.3-fpm
# Stage 0: Base
FROM php:8.3-fpm AS base
# Install PHP dependencies and extensions
# Instala dependências e extensões do PHP
# Install dependencies and extensions
RUN apt-get update && apt-get install -y \
nginx \
nano \
procps \
psmisc \
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
FROM base AS builder
# Copy OPCache configuration
# Copia a configuração do OPCache
COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini
# Install Composer
# Instala o Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Copy webservice configuration
# Copia a configuração do webservice
COPY default.conf /etc/nginx/sites-available/default
RUN mkdir -p /app
# Copy app folder
COPY app/ /app/
# Install composer packages
WORKDIR /app
RUN composer install --no-interaction --optimize-autoloader
# Copy and configure initialization script permissions
# Copia e configura permissões do script de inicialização
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Stage 2: Final
FROM base
RUN mkdir -p /app/cache /app/logs
# Copy necessary files from the builder stage
COPY --from=builder /usr/local/etc/php/conf.d/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
COPY --from=builder /usr/local/bin/composer /usr/local/bin/composer
COPY --from=builder /app /app
# Copy webservice configuration
COPY default.conf /etc/nginx/sites-available/default
# Copy and configure initialization script permissions
COPY docker-entrypoint.sh /usr/local/bin/
COPY bin/cleanup /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh \
&& chmod +x /usr/local/bin/cleanup
# Create cache, database, and logs folders
RUN mkdir -p /app/cache /app/cache/database /app/logs
# Configure base permissions for /app directory
# Configura permissões base para o diretório /app
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"]
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

View file

@ -1,49 +1,40 @@
# 🛠️ 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)
[![Issues](https://img.shields.io/github/issues/manualdousuario/marreta)](https://github.com/manualdousuario/marreta/issues)
Marreta is a tool that removes access barriers and visual distractions!
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)!
## ✨ Features
## ✨ What's Cool?
- Automatically cleans and fixes URLs
- Automatically cleans and corrects URLs
- Removes annoying tracking parameters
- Forces HTTPS to keep everything secure
- Changes user agent to avoid blocks
- Smart DNS
- Keeps HTML clean and optimized
- Fixes relative URLs automatically
- Allows custom styles
- Changes user agent to avoid blocking
- Leaves HTML clean and optimized
- Fixes relative URLs on its own
- Allows you to add your own styles and scripts
- Removes unwanted elements
- Cache, cache!
- Caching, caching!
- Blocks domains you don't want
- Allows custom headers and cookies configuration
- Everything with SSL/TLS
- PHP-FPM
- OPcache enabled
- Direct sharing via PWA on Chrome on Android
- Allows configuring headers and cookies your way
- PHP-FPM and OPcache
## 🐳 Docker
## 🐳 Installing with Docker
### Prerequisites
You only need:
- Docker and docker compose
### Production
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`
If needed
Now modify with your preferences:
`nano docker-compose.yml`
@ -58,188 +49,36 @@ services:
- SITE_NAME=
- SITE_DESCRIPTION=
- SITE_URL=
- LANGUAGE=
```
- `SITE_NAME`: Your Marreta's name
- `SITE_DESCRIPTION`: Tell what it's for
- `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)
- `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)
- `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`
#### Development
1. First, clone the project:
```bash
git clone https://github.com/manualdousuario/marreta/
cd marreta
```
2. Create the configuration file:
```bash
cp app/.env.sample app/.env
```
3. Configure it your way in `app/.env`:
```env
SITE_NAME="Marreta"
SITE_DESCRIPTION="Paywall hammer!"
SITE_URL=http://localhost
DNS_SERVERS=1.1.1.1,8.8.8.8
LOG_LEVEL=WARNING
SELENIUM_HOST=selenium-hub:4444
LANGUAGE=pt-br
```
4. Run everything:
```bash
docker-compose up -d
```
Done! It will be running at `http://localhost` 🎉
## ⚙️ Customization
The configurations are organized in `data/`:
- `domain_rules.php`: Site-specific rules
- `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 `LANGUAGE` environment
### S3 Cache
Cache storage support 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
Selenium integration for processing websites that require javascript or have more advanced protection barriers. To use this functionality, 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 in the hub
- `GRID_BROWSER_TIMEOUT` and `GRID_TIMEOUT`: Timeouts in seconds
After setting up Selenium, make sure to set the `SELENIUM_HOST` variable in your environment to point to the Selenium hub (typically `selenium-hub:4444`).
### Logging System
Logs are stored in `app/logs/app.log` with automatic 7-day rotation.
Log settings available in `.env` or docker:
```env
LOG_LEVEL=WARNING
```
Available log levels:
- DEBUG: Detailed information for debugging
- INFO: General operational information
- WARNING: Warnings that deserve attention (default)
- ERROR: Errors that don't stop operation
- CRITICAL: Critical errors that need immediate attention
## 🛠️ Maintenance
### Logs
View application logs:
```bash
docker-compose logs app
# or directly from the log file
cat app/logs/app.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**: [Shortcuts](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f) integration
-
- 🍎 **Apple**: Integration with [Shortcuts](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f)
---
Made with ❤️! If you have questions or suggestions, open an issue and we'll help! 😉
Thanks to the project [https://github.com/burlesco/burlesco](Burlesco) and [https://github.com/nang-dev/hover-paywalls-browser-extension/](Hover) which was used as a 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)

195
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)
@ -15,35 +15,26 @@ Instancia publica em [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
## ✨ O que tem de legal?
- Limpa e arruma URLs automaticamente
- Limpa e corrige URLs automaticamente
- Remove parâmetros chatos de rastreamento
- Força HTTPS pra manter tudo seguro
- Troca de user agent pra evitar bloqueios
- DNS esperto
- Deixa o HTML limpinho e otimizado
- Conserta URLs relativas sozinho
- Permite colocar seus próprios estilos
- Permite colocar seus próprios estilos e scripts
- Remove elementos indesejados
- Cache, cache!
- Bloqueia domínios que você não quer
- Permite configurar headers e cookies do seu jeito
- Tudo com SSL/TLS
- PHP-FPM
- OPcache ligado
- Compartilhamento direto via PWA no Chrome do Android
- PHP-FPM e OPcache
## 🐳 Docker
## 🐳 Instalando em Docker
### Antes de começar
Só precisa ter instalado:
- Docker e docker compose
### Produção
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`
Se necessario
Agora modifique com suas preferencias:
`nano docker-compose.yml`
@ -58,182 +49,30 @@ 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`
#### Desenvolvimento
1. Primeiro, clona o projeto:
```bash
git clone https://github.com/manualdousuario/marreta/
cd marreta
```
2. Cria o arquivo de configuração:
```bash
cp app/.env.sample app/.env
```
3. Configura do seu jeito no `app/.env`:
```env
SITE_NAME="Marreta"
SITE_DESCRIPTION="Chapéu de paywall é marreta!"
SITE_URL=http://localhost
DNS_SERVERS=1.1.1.1,8.8.8.8
LOG_LEVEL=WARNING
SELENIUM_HOST=selenium-hub:4444
LANGUAGE=pt-br
```
4. Roda tudo:
```bash
docker-compose up -d
```
Pronto! Vai estar rodando em `http://localhost` 🎉
## ⚙️ 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`
### 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 para 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`).
### Sistema de Logs
Os logs são armazenados em `app/logs/app.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
## 🛠️ Manutenção
### Logs
Ver os logs da aplicação:
```bash
docker-compose logs app
# ou diretamente do arquivo de log
cat app/logs/app.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
- 🤖 **Telegram**: [Bot oficial](https://t.me/leissoai_bot)
- 🦊 **Firefox**: Extensão por [Clarissa Mendes](https://claromes.com/pages/whoami) - [Baixar](https://addons.mozilla.org/pt-BR/firefox/addon/marreta/) | [Código fonte](https://github.com/manualdousuario/marreta-extensao)
- 🌀 **Chrome**: Extensão por [Clarissa Mendes](https://claromes.com/pages/whoami) - [Baixar](https://chromewebstore.google.com/detail/marreta/ipelapagohjgjcgpncpbmaaacemafppe) | [Código fonte](https://github.com/manualdousuario/marreta-extensao)
- 🦋 **Bluesky**: Bot por [Joselito](https://bsky.app/profile/joseli.to) - [Perfil](https://bsky.app/profile/marreta.pcdomanual.com) | [Código fonte](https://github.com/manualdousuario/marreta-bot)
- 🍎 **Apple**: Integração ao [Atalhos](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f)
-
---
Feito com ❤️! Se tiver dúvidas ou sugestões, abre uma issue que a gente ajuda! 😉

View file

@ -1,73 +0,0 @@
# Testados/Validos:
## Brasil
https://www1.folha.uol.com.br/poder/2024/11/justica-argentina-emite-mandados-de-prisao-contra-61-foragidos-do-81.shtml
https://g1.globo.com/politica/noticia/2024/11/20/pf-devera-concluir-inquerito-contra-atos-do-8-de-janeiro-nesta-semana.ghtml
https://www.estadao.com.br/politica/planalto-entregou-para-pf-acesso-a-computadores-de-grupo-que-tentou-matar-lula-alckmin-e-moraes/
https://www.correio24horas.com.br/minha-bahia/bahia-registra-primeira-morte-por-coqueluche-em-cinco-anos-1124
https://correio.rac.com.br/campinasermc/iphan-analisa-pedido-de-tombamento-emergencial-da-fazenda-santa-elisa-1.1591950
https://veja.abril.com.br/politica/pt-pede-arquivamento-de-projeto-de-anistia-apos-pf-revelar-plano-para-matar-lula/
https://super.abril.com.br/cultura/consciencia-negra-conheca-os-adinkras-simbolos-africanos-que-estao-no-seu-cotidiano/
https://quatrorodas.abril.com.br/noticias/latinncap-c3aircross-corolla-teste-seguranca/#google_vignette
https://www.abcmais.com/policia/casa-e-destruida-por-incendio-em-novo-hamburgo-2/
https://www.agazeta.com.br/es/cotidiano/negros-sao-a-maioria-entre-pobres-e-analfabetos-no-es-aponta-estudo-1124
https://www.gazetadopovo.com.br/republica/moraes-nao-tem-condicoes-de-julgar-suposto-plano-golpista-diz-rogerio-marinho/
https://revistagalileu.globo.com/colunistas/the-conversation/noticia/2024/11/por-que-nos-sentimos-melhor-quando-acordamos-sem-despertador-neurologista-explica.ghtml
https://oglobo.globo.com/epoca/noticia/2024/09/07/moeda-rara-do-seculo-xviii-encontrada-dentro-de-lata-de-caramelo-sera-leiloada-na-inglaterra-e-pode-valer-ate-r-220-mil.ghtml
https://valor.globo.com/financas/esg/noticia/2024/11/18/anbima-propoe-novas-regras-para-ofertas-de-titulos-de-renda-fixa-sustentaveis.ghtml
https://www.seudinheiro.com/2024/patrocinado/conteudo-eqi/fii-arrecada-r-137-milhoes-com-venda-de-galpoes-e-e-recomendado-por-analistas-veja/
https://www.nsctotal.com.br/noticias/veja-os-bairros-mais-populosos-de-florianopolis-segundo-o-ibge
https://exame.com/invest/mercados/nvidia-bate-consenso-mais-uma-vez-e-reporta-lucro-de-us-193-bilhoes-no-terceiro-trimestre/
https://www.dgabc.com.br/Noticia/4177263/casa-de-acolhimento-a-mulheres-vitimas-de-violencia-e-invadida-em-sto-andre
https://diarinho.net/materia/657650/Gasolina-subiu-mais-de-R--0-50-desde-janeiro--veja-os-precos-da-pesquisa-
https://www.em.com.br/gerais/2024/11/6993175-incendio-fecha-shopping-na-regiao-centro-sul-de-bh.html
https://mais.opovo.com.br/colunistas/ariadne-araujo/2024/11/20/quem-quer-casar-com-um-heroi-de-guerra-ucraniano.html
https://www.folhadelondrina.com.br/folha-2/cena-musical-em-londrina-idosos-no-palco-e-na-plateia-3267044e.html?d=1
https://www.uol.com.br/esporte/futebol/ultimas-noticias/2024/11/20/bahia-x-palmeiras-campeonato-brasileiro-2024-rodada-34.htm
https://www.opovo.com.br/noticias/politica/2024/11/21/atuacao-de-faccoes-nas-eleicoes-teve-compra-de-votos-e-ate-enviado-do-rj-para-intimidacoes-no-ceara.html
https://flatout.com.br/o-impossivel-acidente-de-dan-wheldon-uma-analise-medica/
https://tab.uol.com.br/noticias/redacao/2024/11/27/roubos-suicidios-e-assassinatos-casos-extremos-do-vicio-em-apostas.htm
https://www.jota.info/coberturas-especiais/g20-brasil/presidente-sul-africano-quer-modelo-de-consenso-amplo-para-g20-na-era-trump
https://quatrorodas.abril.com.br/noticias/chinesa-gac-investe-r-120-milhoes-para-produzir-motor-hibrido-flex-no-brasil/
https://www.intercept.com.br/2024/12/05/uerj-curso-brasil-paralelo/
https://www.agazeta.com.br/concursos-e-empregos/concursos/prefeitura-de-cariacica-vai-abrir-concurso-com-salario-de-ate-r-78-mil-1224
https://natelinha.uol.com.br/televisao/2024/12/05/boninho-fecha-com-o-sbt-para-novo-reality-show-219855.php
https://gamarevista.uol.com.br/semana/deu-vontade-de-ter-outra-vida/novas-formas-abandonar/
https://tecnoblog.net/noticias/cor-do-ano-esta-em-celulares-da-motorola-que-serao-vendidos-no-brasil/
https://gauchazh.clicrbs.com.br/pioneiro/policia/noticia/2024/11/caxias-do-sul-podera-fazer-emprestimo-de-ate-us-40-milhoes-para-melhorias-tecnologicas-em-educacao-seguranca-e-servicos-municipais-cm3q9yn870051014fzz77djqz.html
https://diplomatique.org.br/saude-unica-em-tempos-de-crise-democratica/
https://oantagonista.com.br/brasil/lewandowski-insiste-na-pec-da-seguranca/
https://jornaldebrasilia.com.br/noticias/politica-e-poder/lula-aguarda-pt-para-troca-em-pastas-chefiadas-por-petistas-em-reforma-ministerial/
https://opopular.com.br/cidades/ex-secretario-de-saude-de-goiania-deixa-hospital-e-volta-para-a-cadeia-1.3207162
https://www.cartacapital.com.br/politica/surpresa-natalina/
https://seucreditodigital.com.br/123milhas-devera-apresentar-plano-de-recuperacao-ainda-este-mes/
https://www.matinaljornalismo.com.br/matinal/reportagem-matinal/vazao-guaiba-porto-alegre/
## Internacional
https://www.nytimes.com/2024/11/20/us/politics/matt-gaetz-venmo-payments-sex.html
https://www.ft.com/content/cab2e834-e5fa-4dd0-b757-76c243ac2eeb
https://foreignpolicy.com/2024/11/20/trump-energy-policy-electric-vehicles-power-grid-infrastructure-oil-gas-climate/
https://www.wired.com/story/waymo-robotaxi-driverless-future/
https://www.forbes.com/sites/cyrusfarivar/2024/11/20/under-trump-tariffs-made-in-vietnam-will-be-the-new-made-in-china/
https://observador.pt/especiais/mais-formacao-investimento-na-frota-reforco-dos-tecnicos-de-emergencia-e-autonomia-o-que-precisa-o-inem-segundo-varios-especialistas/
https://www.businessinsider.com/bluesky-user-growth-social-coo-servers-twitter-elon-musk-x-2024-11
https://cooking.nytimes.com/recipes/1016583-perfect-instant-ramen?smid=bsky-nytimes
https://www.latercera.com/nacional/noticia/gracias-por-arruinar-mi-vida-maldito-el-contenido-de-los-25-correos-que-boric-entrego-a-fiscal-que-investiga-denuncia/HL44T5EN55H6DHADGMORNCCNBY/
https://www.liberation.fr/culture/cinema/les-nouveaux-films-de-monstres-de-plus-en-plus-repugnangnan-20241106_BMQANPKL3FEPNLFIAXP5HIUSIQ/
https://www.haaretz.com/israel-news/2024-12-04/ty-article-live/idf-claims-hezbollah-liaison-to-syrian-army-killed-in-damascus-airstrike/00000193-8fa5-d2e5-a9b3-bfffbbc70000
https://www.haaretz.co.il/news/politics/2024-11-20/ty-article/.premium/00000193-4b4c-d383-abbb-ebffe90d0000
https://www.ajc.com/news/crime/six-more-ysl-defendants-may-slip-charges/CWUGV7CVHRCMZGDFSWEMQLLGWM/
https://expresso.pt/arquivo/Expresso/Economia/2024-12-05-tap-esta-num-ano-extremamente-positivo-mas-luis-rodrigues-anteve-desafios-significativos-para-2025-5f0d0733
https://www.foreignaffairs.com/india/india-hoping-trump-bump
https://www.latercera.com/la-tercera-pm/noticia/duro-reves-al-nuevo-criterio-de-dorothy-perez-en-la-antesala-de-cambios-de-equipos-municipales-y-ceses-de-contrata-la-corte-manda-mensaje-a-contralora/G5ADQKPSSBBANAGDVK6TETP3RU/
https://www.wired.com/story/operation-destabilise-money-laundering/
https://reuters.com/world/us/internet-sleuths-hunt-clues-murder-unitedhealths-brian-thompson-2024-12-05/
https://www.washingtonpost.com/world/2024/12/03/martial-law-south-korea-explained/
https://www.npr.org/2024/12/06/nx-s1-5219927/unitedhealthcare-ceo-shooting-new-york
https://www.bloomberg.com/news/articles/2024-12-07/south-korea-impeachment-vote-drags-on-after-ruling-party-boycott?srnd=homepage-americas
https://www.leparisien.fr/politique/le-gouvernement-barnier-censure-emmanuel-macron-de-nouveau-face-a-une-impasse-05-12-2024-KNZVD5MGOJGIPEONBQEYSACOLI.php
https://www.businessinsider.com/google-unveiled-quantum-computer-chip-willow-2024-12
https://theverge.com/2024/12/11/24318164/lord-of-the-rings-war-of-the-rohirrim-review
https://www.sabado.pt/portugal/detalhe/lider-sindical-da-psp-apanhado-numa-investigacao-da-pj
https://www.cmjornal.pt/portugal/detalhe/motorista-da-aeroporto-parque-destroi-carro-de-colecao-de-cliente-e-quer-pagar-tuta-e-meia
https://www.nzherald.co.nz/entertainment/nz-theatre-legend-tim-bray-on-cancer-chemo-and-the-man-whos-holding-him-together/ESPBIQ7YEFGEBMGPWPB6XBEY5U/
https://www.elcorreo.com/athletic/uriarte-estalla-olmo-alucinante-deco-responde-preocupe-20250108203120-nt.html

View file

@ -1,43 +1,49 @@
# Arquivo de exemplo para configuração de variáveis de ambiente
# Copie este arquivo para .env e ajuste os valores conforme necessário
# Sample file for environment variable configuration
# Copy this file to .env and adjust the values as needed
# Nome do site exibido no cabeçalho e meta tags
SITE_NAME=Marreta
# Site name displayed in the header and meta tags
SITE_NAME=Marreta
# Descrição do site usada em meta tags e SEO
SITE_DESCRIPTION="Chapéu de paywall é marreta!"
# Site description used in meta tags and SEO
SITE_DESCRIPTION="Chapéu de paywall é marreta!"
# Idioma do site (opções disponíveis: pt-br, en, es, de-de)
# pt-br = Português do Brasil
# en = English
# es = Español
# de-de = German
LANGUAGE=pt-br
# Site language (available options: pt-br, en, es, de-de, ru-ru)
# pt-br = Brazilian Portuguese
# en = English
# es = Spanish
# de-de = German
# ru-ru = Russian
LANGUAGE=pt-br
# URL base do site (sem barra no final)
# Use https://localhost para desenvolvimento local
SITE_URL=https://localhost
# Base URL of the site (without a trailing slash)
# Use https://localhost for local development
SITE_URL=https://localhost
# Lista de servidores DNS para resolução de domínios
# Recomendado: AdGuard DNS (94.140.14.14, 94.140.15.15)
DNS_SERVERS='94.140.14.14,94.140.15.15'
# List of DNS servers for domain resolution
# Recommended: AdGuard DNS (94.140.14.14, 94.140.15.15)
DNS_SERVERS='94.140.14.14,94.140.15.15'
# Modo sem cache (true/false)
# Quando ativo, desativa o cache do sistema
DISABLE_CACHE=false
# Disable cache mode (true/false)
# When enabled, system caching is turned off
DISABLE_CACHE=false
# Configurações de Cache S3
S3_CACHE_ENABLED=false
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_BUCKET=
S3_REGION=us-east-1
S3_FOLDER=cache/
S3_ACL=private
S3_ENDPOINT=
# S3 Cache Settings
S3_CACHE_ENABLED=false
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_BUCKET=
S3_REGION=us-east-1
S3_FOLDER=cache/
S3_ACL=private
S3_ENDPOINT=
# Configurações do Selenium
SELENIUM_HOST=localhost:4444
# Selenium Configuration
SELENIUM_HOST=localhost:4444
# Configurações de Debug
DEBUG=true
# 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

114
app/Gulpfile.js Normal file
View file

@ -0,0 +1,114 @@
"use strict";
const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const clean_css = require('gulp-clean-css');
const sourcemaps = require('gulp-sourcemaps');
const imagemin = require('gulp-imagemin');
const webp = require('gulp-webp');
const newer = require('gulp-newer');
const rename = require('gulp-rename');
const ttf2woff = require('gulp-ttf2woff');
const ttf2woff2 = require('gulp-ttf2woff2');
const ttf2eot = require('gulp-ttf2eot');
const svgmin = require('gulp-svgmin');
const paths = {
styles: {
src: 'assets/scss/*.scss',
dest: 'dist/css'
},
scripts: {
src: 'assets/js/*.js',
dest: 'dist/js'
},
images: {
src: 'assets/images/**/*',
dest: 'dist/images'
},
fonts: {
src: 'assets/fonts/**/*.ttf',
dest: 'dist/fonts'
},
icons: {
src: 'assets/icons/**/*.svg',
dest: 'dist/icons'
}
};
function styles() {
return gulp.src(paths.styles.src)
.pipe(sourcemaps.init())
.pipe(sass({
outputStyle: "expanded",
includePaths: ['./node_modules']
}))
.pipe(concat('style.css'))
.pipe(clean_css())
.pipe(gulp.dest(paths.styles.dest))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paths.styles.dest))
}
function scripts() {
return gulp.src(paths.scripts.src)
.pipe(sourcemaps.init())
.pipe(concat('scripts.js'))
.pipe(uglify())
.pipe(gulp.dest(paths.scripts.dest))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paths.scripts.dest))
}
function images() {
return gulp.src(paths.images.src)
.pipe(newer(paths.images.dest))
.pipe(imagemin())
.pipe(gulp.dest(paths.images.dest))
.pipe(webp())
.pipe(gulp.dest(paths.images.dest))
}
function icons() {
return gulp.src(paths.icons.src)
.pipe(newer(paths.icons.dest))
.pipe(svgmin())
.pipe(gulp.dest(paths.icons.dest))
}
function fonts() {
return gulp.src(paths.fonts.src)
.pipe(newer(paths.fonts.dest))
.pipe(ttf2woff())
.pipe(rename({ extname: '.woff' }))
.pipe(gulp.dest(paths.fonts.dest))
.pipe(gulp.src(paths.fonts.src))
.pipe(ttf2woff2())
.pipe(rename({ extname: '.woff2' }))
.pipe(gulp.dest(paths.fonts.dest))
.pipe(gulp.src(paths.fonts.src))
.pipe(ttf2eot())
.pipe(rename({ extname: '.eot' }))
.pipe(gulp.dest(paths.fonts.dest))
.pipe(gulp.src(paths.fonts.src))
.pipe(gulp.dest(paths.fonts.dest));
}
function watch() {
gulp.watch(paths.styles.src, styles);
gulp.watch(paths.scripts.src, scripts);
gulp.watch(paths.images.src, images);
gulp.watch(paths.fonts.src, fonts);
gulp.watch(paths.icons.src, icons);
}
exports.default = gulp.series(
gulp.parallel(styles, scripts, images, fonts, icons),
watch
);

0
app/assets/.dockerignore Normal file
View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-android2" viewBox="0 0 16 16">
<path d="m10.213 1.471.691-1.26q.069-.124-.048-.192-.128-.057-.195.058l-.7 1.27A4.8 4.8 0 0 0 8.005.941q-1.032 0-1.956.404l-.7-1.27Q5.281-.037 5.154.02q-.117.069-.049.193l.691 1.259a4.25 4.25 0 0 0-1.673 1.476A3.7 3.7 0 0 0 3.5 5.02h9q0-1.125-.623-2.072a4.27 4.27 0 0 0-1.664-1.476ZM6.22 3.303a.37.37 0 0 1-.267.11.35.35 0 0 1-.263-.11.37.37 0 0 1-.107-.264.37.37 0 0 1 .107-.265.35.35 0 0 1 .263-.11q.155 0 .267.11a.36.36 0 0 1 .112.265.36.36 0 0 1-.112.264m4.101 0a.35.35 0 0 1-.262.11.37.37 0 0 1-.268-.11.36.36 0 0 1-.112-.264q0-.154.112-.265a.37.37 0 0 1 .268-.11q.155 0 .262.11a.37.37 0 0 1 .107.265q0 .153-.107.264M3.5 11.77q0 .441.311.75.311.306.76.307h.758l.01 2.182q0 .414.292.703a.96.96 0 0 0 .7.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h1.343v2.182q0 .414.292.703a.97.97 0 0 0 .71.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h.76q.436 0 .749-.308.31-.307.311-.75V5.365h-9zm10.495-6.587a.98.98 0 0 0-.702.278.9.9 0 0 0-.293.685v4.063q0 .406.293.69a.97.97 0 0 0 .702.284q.42 0 .712-.284a.92.92 0 0 0 .293-.69V6.146a.9.9 0 0 0-.293-.685 1 1 0 0 0-.712-.278m-12.702.283a1 1 0 0 1 .712-.283q.41 0 .702.283a.9.9 0 0 1 .293.68v4.063a.93.93 0 0 1-.288.69.97.97 0 0 1-.707.284 1 1 0 0 1-.712-.284.92.92 0 0 1-.293-.69V6.146q0-.396.293-.68"/>
</svg>

After

(image error) Size: 1.4 KiB

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16">
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
</svg>

After

(image error) Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="black" d="M0 48C0 21.5 21.5 0 48 0l0 48V441.4l130.1-92.9c8.3-6 19.6-6 27.9 0L336 441.4V48H48V0H336c26.5 0 48 21.5 48 48V488c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5 37.9 507.5c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>

After

(image error) Size: 309 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 568 501"><path fill="currentColor" d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"></path></svg>

After

(image error) Size: 677 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-chrome" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M16 8a8 8 0 0 1-7.022 7.94l1.902-7.098a3 3 0 0 0 .05-1.492A3 3 0 0 0 10.237 6h5.511A8 8 0 0 1 16 8M0 8a8 8 0 0 0 7.927 8l1.426-5.321a3 3 0 0 1-.723.255 3 3 0 0 1-1.743-.147 3 3 0 0 1-1.043-.7L.633 4.876A8 8 0 0 0 0 8m5.004-.167L1.108 3.936A8.003 8.003 0 0 1 15.418 5H8.066a3 3 0 0 0-1.252.243 2.99 2.99 0 0 0-1.81 2.59M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4"/>
</svg>

After

(image error) Size: 525 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>

After

(image error) Size: 308 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-80c-13.3 0-24 10.7-24 24s10.7 24 24 24zm-24-72h128c13.3 0 24-10.7 24-24s-10.7-24-24-24H192c-13.3 0-24 10.7-24 24s10.7 24 24 24z"/></svg>

After

(image error) Size: 319 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-firefox" viewBox="0 0 16 16">
<path d="M13.384 3.408c.535.276 1.22 1.152 1.556 1.963a8 8 0 0 1 .503 3.897l-.009.077-.026.224A7.758 7.758 0 0 1 .006 8.257v-.04q.025-.545.114-1.082c.01-.074.075-.42.09-.489l.01-.051a6.6 6.6 0 0 1 1.041-2.35q.327-.465.725-.87.35-.358.758-.65a1.5 1.5 0 0 1 .26-.137c-.018.268-.04 1.553.268 1.943h.003a5.7 5.7 0 0 1 1.868-1.443 3.6 3.6 0 0 0 .021 1.896q.105.07.2.152c.107.09.226.207.454.433l.068.066.009.009a2 2 0 0 0 .213.18c.383.287.943.563 1.306.741.201.1.342.168.359.193l.004.008c-.012.193-.695.858-.933.858-2.206 0-2.564 1.335-2.564 1.335.087.997.714 1.839 1.517 2.357a4 4 0 0 0 .439.241q.114.05.228.094c.325.115.665.18 1.01.194 3.043.143 4.155-2.804 3.129-4.745v-.001a3 3 0 0 0-.731-.9 3 3 0 0 0-.571-.37l-.003-.002a2.68 2.68 0 0 1 1.87.454 3.92 3.92 0 0 0-3.396-1.983q-.116.001-.23.01l-.042.003V4.31h-.002a4 4 0 0 0-.8.14 7 7 0 0 0-.333-.314 2 2 0 0 0-.2-.152 4 4 0 0 1-.088-.383 5 5 0 0 1 1.352-.289l.05-.003c.052-.004.125-.01.205-.012C7.996 2.212 8.733.843 10.17.002l-.003.005.003-.001.002-.002h.002l.002-.002h.015a.02.02 0 0 1 .012.007 2.4 2.4 0 0 0 .206.48q.09.153.183.297c.49.774 1.023 1.379 1.543 1.968.771.874 1.512 1.715 2.036 3.02l-.001-.013a8 8 0 0 0-.786-2.353"/>
</svg>

After

(image error) Size: 1.3 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5"/>
</svg>

After

(image error) Size: 341 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="black" d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>

After

(image error) Size: 872 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="black" d="M283.9 378.6l18.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.9s0 0 0 0L122 208.8s0 0 0 0L17.7 156.6C1.9 148.7-4.5 129.5 3.4 113.7l40-80C48.8 22.8 59.9 16 72 16l120 0c5 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.6zM107.6 237.4l85.9 42.9L90.9 485.5c-11.9 23.7-40.7 33.3-64.4 21.5S-6.8 466.2 5.1 442.5L107.6 237.4z"/></svg>

After

(image error) Size: 838 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 0 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c0 0 0 0 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.8c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/></svg>

After

(image error) Size: 873 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-telegram" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.287 5.906q-1.168.486-4.666 2.01-.567.225-.595.442c-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294q.39.01.868-.32 3.269-2.206 3.374-2.23c.05-.012.12-.026.166.016s.042.12.037.141c-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8 8 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629q.14.092.27.187c.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.4 1.4 0 0 0-.013-.315.34.34 0 0 0-.114-.217.53.53 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09"/>
</svg>

After

(image error) Size: 711 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>

After

(image error) Size: 397 B

View file

Before

(image error) Size: 7.2 KiB

After

(image error) Size: 7.2 KiB

View file

Before

(image error) Size: 4.2 KiB

After

(image error) Size: 4.2 KiB

View file

Before

(image error) Size: 16 KiB

After

(image error) Size: 16 KiB

BIN
app/assets/images/wall.png Normal file

Binary file not shown.

After

(image error) Size: 53 KiB

View file

@ -1,130 +1,104 @@
/**
* JavaScript functions for form validation and error handling
* Funções JavaScript para validação de formulário e manipulação de erros
*/
/**
* Validates the form before submission
*
* Checks:
* - If URL is not empty
* - If URL starts with http:// or https://
* - If URL has a valid format
*
* @returns {boolean} True if URL is valid, False otherwise
*
* Valida o formulário antes do envio
*
* Verifica:
* - Se a URL não está vazia
* - Se a URL começa com http:// ou https://
* - Se a URL tem um formato válido
*
* @returns {boolean} True se a URL for válida, False caso contrário
*/
function validateForm() {
const urlInput = document.getElementById('url');
const submitButton = document.querySelector('button[type="submit"]');
const url = urlInput.value.trim();
// Check if URL is not empty
// Verifica se a URL não está vazia
if (!url) {
showError('Por favor, insira uma URL');
return false;
}
// Check if URL starts with http:// or https://
// Verifica se a URL começa com http:// ou https://
if (!/^https?:\/\//i.test(url)) {
showError('A URL deve começar com http:// ou https://');
return false;
}
// Try to create a URL object to validate format
// Tenta criar um objeto URL para validar o formato
try {
new URL(url);
} catch (e) {
showError('Formato de URL inválido');
return false;
}
// Disable input and button
// Desabilita o input e o botão
urlInput.readonly = true;
submitButton.disabled = true;
// Add Tailwind disabled classes
// Adiciona classes de disabled do Tailwind
submitButton.classList.add('cursor-wait', 'disabled:bg-blue-400');
submitButton.classList.remove('hover:bg-blue-700');
urlInput.classList.add('cursor-wait', 'disabled:bg-gray-50', 'focus:outline-none');
// Add loading state to button
// Adiciona estado de loading ao botão
submitButton.innerHTML = `
<img src="assets/svg/search.svg" class="w-6 h-6 mr-3" alt="Search">
Analisando...
`;
return true;
}
/**
* Displays an error message below the form
*
* Removes any existing error message before displaying the new one.
* The message is displayed with an error icon and red formatting.
*
* @param {string} message Error message to be displayed
*
* Exibe uma mensagem de erro abaixo do formulário
*
* Remove qualquer mensagem de erro existente antes de exibir a nova.
* A mensagem é exibida com um ícone de erro e formatação em vermelho.
*
* @param {string} message Mensagem de erro a ser exibida
*/
function showError(message) {
const form = document.getElementById('urlForm');
const existingError = form.querySelector('.error-message');
// Remove previous error message if it exists
// Remove mensagem de erro anterior se existir
if (existingError) {
existingError.remove();
}
// Create and add new error message
// Cria e adiciona nova mensagem de erro
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message mt-4 text-base text-red-600';
errorDiv.innerHTML = `
<img src="assets/svgs/error.svg" class="inline-block w-5 h-5 mr-2" alt="Error icon">
${message}`;
form.appendChild(errorDiv);
}
/**
* Service Worker registration for PWA functionality
* Registers a service worker to enable offline capabilities and PWA features
*
* Registro do Service Worker para funcionalidade PWA
* Registra um service worker para habilitar recursos offline e funcionalidades PWA
*/
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(() => {
// Service Worker registered successfully
// Service Worker registrado com sucesso
})
.catch(() => {
// Service Worker registration failed
// Falha no registro do Service Worker
});
});
}
/**
* Header toggle menus
*/
document.addEventListener('DOMContentLoaded', function () {
const integration = document.querySelector('.integration');
const integrationToggle = document.querySelector('.integration__toggle');
const extension = document.querySelector('.extension');
const extensionToggle = document.querySelector('.extension__toggle');
// Function to close all menus
const closeAllMenus = () => {
integration.classList.remove('open');
extension.classList.remove('open');
};
// Function to close other menus except the one passed
const closeOtherMenus = (exceptMenu) => {
if (exceptMenu !== integration) {
integration.classList.remove('open');
}
if (exceptMenu !== extension) {
extension.classList.remove('open');
}
};
integrationToggle.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent click from bubbling to document
closeOtherMenus(integration);
integration.classList.toggle('open');
});
extensionToggle.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent click from bubbling to document
closeOtherMenus(extension);
extension.classList.toggle('open');
});
// Prevent clicks inside menus from closing them
integration.addEventListener('click', (e) => {
e.stopPropagation();
});
extension.addEventListener('click', (e) => {
e.stopPropagation();
});
// Close menus when clicking outside
document.addEventListener('click', () => {
closeAllMenus();
});
// Remove toasty elements when clicked
document.addEventListener('click', (e) => {
const toastyElement = e.target.closest('.toasty');
if (toastyElement) {
toastyElement.remove();
}
});
// Toggle header open class when open-nav is clicked
document.addEventListener('click', (e) => {
const openNavElement = e.target.closest('.open-nav');
if (openNavElement) {
const header = document.querySelector('header');
if (header.classList.contains('open')) {
header.classList.remove('open');
} else {
header.classList.add('open');
}
}
});
// 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

@ -0,0 +1,35 @@
@use "mixin";
body {
font-family: var(--font-family-inter);
font-size: var(--font-size);
font-weight: var(--font-weight);
line-height: var(--line-height);
color: var(--text);
text-align: left;
background-color: var(--background);
min-width: 320px;
}
a {
text-decoration: none;
color: var(--link);
&:hover {
text-decoration: none;
color: var(--link-lighten);
}
}
.container {
padding-right: var(--container_spacing);
padding-left: var(--container_spacing);
margin-right: auto;
margin-left: auto;
overflow: hidden;
@include mixin.devices(desktop) {
max-width: 1248px;
}
}

View file

@ -0,0 +1,35 @@
@font-face {
font-family: 'inter';
src: url('../dist/fonts/inter-500.eot');
src: local('Inter Medium'), local('Inter-Medium'),
url('../dist/fonts/inter-500.woff2') format('woff2'),
url('../dist/fonts/inter-500.woff') format('woff'),
url('../dist/fonts/inter-500.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'inter';
src: url('../dist/fonts/inter-600.eot');
src: local('Inter SemiBold'), local('Inter-SemiBold'),
url('../dist/fonts/inter-600.woff2') format('woff2'),
url('../dist/fonts/inter-600.woff') format('woff'),
url('../dist/fonts/inter-600.ttf') format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'unna';
src: url('../dist/fonts/unna-400.eot');
src: local('Unna Regular'), local('Unna-Regular'),
url('../dist/fonts/unna-400.woff2') format('woff2'),
url('../dist/fonts/unna-400.woff') format('woff'),
url('../dist/fonts/unna-400.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}

View file

@ -0,0 +1,43 @@
@use "mixin";
// https://icons.getbootstrap.com/
// https://icofont.com/icons
// https://jakearchibald.github.io/svgomg/
// https://yoksel.github.io/url-encoder/
// https://codepen.io/sosuke/pen/Pjoqqp
.icon {
display: inline-block;
vertical-align: -0.125em;
fill: currentcolor;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-position: center center;
background-size: 100% auto;
&--bookmark {
background-size: auto 100% !important;
}
}
@include mixin.icon('marreta', 'invert(43%) sepia(74%) saturate(2288%) hue-rotate(201deg) brightness(98%) contrast(97%)');
@include mixin.icon('bookmark', 'invert(80%) sepia(46%) saturate(1512%) hue-rotate(340deg) brightness(106%) contrast(97%)');
@include mixin.icon('android', 'invert(72%) sepia(34%) saturate(778%) hue-rotate(32deg) brightness(97%) contrast(88%)');
@include mixin.icon('apple', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
@include mixin.icon('bsky', 'invert(47%) sepia(66%) saturate(4445%) hue-rotate(195deg) brightness(98%) contrast(104%)');
@include mixin.icon('telegram', 'invert(61%) sepia(86%) saturate(1341%) hue-rotate(166deg) brightness(96%) contrast(85%)');
@include mixin.icon('chrome', 'invert(40%) sepia(90%) saturate(1163%) hue-rotate(203deg) brightness(104%) contrast(92%)');
@include mixin.icon('firefox', 'invert(57%) sepia(43%) saturate(1854%) hue-rotate(360deg) brightness(102%) contrast(106%)');
@include mixin.icon('link', 'invert(53%) sepia(12%) saturate(17%) hue-rotate(39deg) brightness(94%) contrast(91%)');
@include mixin.icon('refresh', 'invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%)');
@include mixin.icon('error', 'invert(30%) sepia(58%) saturate(3703%) hue-rotate(336deg) brightness(90%) contrast(91%)');
@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('paste', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');

View file

@ -0,0 +1,23 @@
@use "sass:math";
@use "sass:color";
@mixin create-color($name, $hex) {
--#{$name}: #{$hex};
--#{$name}-lighten: #{color.adjust($hex, $lightness: 5%)};
--#{$name}-darken: #{color.adjust($hex, $lightness: -10%)};
}
@mixin devices($breakpoint) {
@if $breakpoint == desktop {
@media only screen and (min-width: 1200px) {
@content;
}
}
}
@mixin icon($name, $filter) {
.icon--#{$name} {
background-image: url("../dist/icons/#{$name}.svg");
filter: #{$filter};
}
}

View file

@ -0,0 +1,48 @@
@use "mixin";
:root {
--font-family-sans-serif: -apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
"Liberation Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
--font-family-monospace: SFMono-Regular,
Menlo, Monaco,
Consolas,
"Liberation Mono",
"Courier New",
monospace;
--font-family-inter: "inter";
--font-family-unna: "unna";
--font-size: 16px;
--font-weight: 500;
--line-height: 160%;
@include mixin.create-color('marreta', #3B82F6);
@include mixin.create-color('text', #484848);
@include mixin.create-color('textmuted', #818181);
@include mixin.create-color('link', #3B82F6);
--container_spacing: 24px;
@include mixin.devices(desktop) {
--container_spacing: 64px;
}
}
html {
scroll-behavior: smooth;
}
@media screen and (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}

641
app/assets/scss/home.scss Normal file
View file

@ -0,0 +1,641 @@
@use "mixin";
body {
padding: 42px 0 0 0;
@include mixin.devices(desktop) {
padding: 42px 0 52px 0;
}
}
.toasty {
border-radius: 8px;
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.05);
box-shadow: 0px 10px 15px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.04);
box-shadow: 0px 20px 25px 0px rgba(0, 0, 0, 0.1);
position: absolute;
top: 16px;
left: 16px;
right: 16px;
color: #fff;
z-index: 1000;
width: auto;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
.close {
.icon {
width: 14px;
height: 14px;
}
}
p {
padding: 0;
margin: 0;
font-size: 12px;
line-height: 1.3em;
}
@include mixin.devices(desktop) {
max-width: 480px;
left: 50%;
margin-left: -256px;
}
&--error {
background-color: rgb(247, 102, 97);
}
&--warning {
background-color: rgb(247, 152, 97);
}
}
header {
display: grid;
grid-template-columns: auto 1fr 1fr;
align-items: center;
padding: 0 0 42px 0;
@include mixin.devices(desktop) {
grid-template-columns: 1fr 2fr 1fr;
}
&.open {
.extension {
&__toggle {
background-color: #000;
}
}
.open-nav {
.icon {
&--hamburguer {
display: none;
}
&--close {
display: block;
}
}
}
nav {
display: flex;
}
.integration__menu {
.icon {
filter: invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%);
}
}
}
.open-nav {
cursor: pointer;
position: relative;
z-index: 501;
padding-right: 16px;
.icon {
width: 24px;
height: 24px;
&--hamburguer {
display: block;
}
&--close {
display: none;
}
}
@include mixin.devices(desktop) {
display: none;
}
}
.brand {
display: flex;
align-items: center;
.icon {
margin-right: 6px;
&--marreta {
width: 32px;
height: 32px;
}
}
h1 {
font-family: var(--font-family-unna);
color: #000;
}
}
nav {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--marreta);
padding: var(--container_spacing) var(--container_spacing) calc(4*var(--container_spacing)) var(--container_spacing);
z-index: 500;
align-items: flex-end;
flex-direction: column;
justify-content: end;
&> * {
width: 100%;
@include mixin.devices(desktop) {
width: auto;
}
}
@include mixin.devices(desktop) {
opacity: 1;
display: flex;
position: relative;
left: initial;
right: initial;
bottom: initial;
top: initial;
background-color: transparent;
padding: 0;
justify-content: center;
align-items: normal;
flex-direction:initial;
gap: 48px;
}
a {
display: block;
font-size: 24px;
padding: 16px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.24);
color: #fff;
text-decoration: none;
@include mixin.devices(desktop) {
color: #333;
font-size: initial;
padding: 0;
border-bottom: 0;
}
&:hover {
color: #fff;
@include mixin.devices(desktop) {
color: #007bff;
}
}
}
.integration {
position: relative;
padding-top: 32px;
@include mixin.devices(desktop) {
padding-top: 0;
}
&__toggle {
background: none;
border: none;
color: rgba(255,255,255,0.5);
padding: 0;
@include mixin.devices(desktop) {
color: #333;
cursor: pointer;
}
&:hover {
color: rgba(255,255,255,0.5);
@include mixin.devices(desktop) {
color: #007bff;
}
}
}
&__menu {
@include mixin.devices(desktop) {
position: absolute;
top: 110%;
left: 0;
border-radius: 16px;
background-color: #F4F4F5;
border: 4px solid #F4F4F5;
z-index: 10;
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.05);
box-shadow: 0px 10px 15px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.04);
box-shadow: 0px 20px 25px 0px rgba(0, 0, 0, 0.1);
transition: max-height 0.8s cubic-bezier(0.16, 1, 0.3, 1);
max-height: 0;
opacity: 0;
overflow: hidden;
}
a {
font-size: 14px;
border-bottom: 0;
margin-top: 8px;
padding: 16px 0 0 0;
display: flex;
align-items: center;
color: #fff;
font-weight: 600;
@include mixin.devices(desktop) {
background-color: #fff;
margin-top: 0;
margin-bottom: 4px;
padding: 8px 16px;
font-size: var(--font-size);
color: var(--text);
}
&:first-child {
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
&:last-child {
margin-bottom: 0;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
}
&:hover {
color: var(--marreta);
}
span {
display: inline-block;
}
}
.icon {
width: 22px;
height: 22px;
order: 1;
@include mixin.devices(desktop) {
order: 2;
width: 16px;
height: 16px;
}
}
.name {
order: 2;
line-height: 1em;
padding-left: 12px;
@include mixin.devices(desktop) {
order: 1;
padding-left: 0;
width: 140px;
line-height: var(--line-height);
}
}
}
&.open {
.integration__menu {
max-height: 200px;
opacity: 1;
}
.arrow {
top: 1px;
transform: rotate(-45deg);
}
}
.arrow {
display: none;
position: relative;
top: -3px;
content: "";
width: 6px;
height: 6px;
border-right: 2px solid black;
border-top: 2px solid black;
transform: rotate(135deg);
margin-right: 0;
margin-left: 16px;
@include mixin.devices(desktop) {
display: inline-block;
}
}
}
}
.extension {
display: flex;
justify-content: flex-end;
position: relative;
z-index: 501;
&__toggle {
background-color: var(--marreta);
border-radius: 40px;
border: 0;
cursor: pointer;
color: #FFF;
font-weight: 600;
padding: 12px 24px;
line-height: 1.3em;
&:hover {
background-color: var(--marreta-darken);
}
}
&__menu {
position: absolute;
top: 110%;
right: 0;
border-radius: 16px;
background-color: #F4F4F5;
border: 4px solid #F4F4F5;
z-index: 10;
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.05);
box-shadow: 0px 10px 15px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.04);
box-shadow: 0px 20px 25px 0px rgba(0, 0, 0, 0.1);
transition: max-height 0.8s cubic-bezier(0.16, 1, 0.3, 1);
max-height: 0;
opacity: 0;
overflow: hidden;
a {
margin-bottom: 4px;
&:first-child {
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
&:last-child {
margin-bottom: 0;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
}
color: var(--text);
font-weight: 600;
display: block;
padding: 8px 16px;
background-color: #fff;
display: flex;
align-items: center;
&:hover {
color: var(--marreta);
}
span {
display: inline-block;
}
}
.name {
width: 140px;
}
}
&.open {
.extension__toggle {
background-color: #F4F4F5;
color: var(--textmuted);
}
.extension__menu {
max-height: 200px;
opacity: 1;
}
}
}
}
main {
.description {
position: relative;
z-index: 3;
font-family: var(--font-family-unna);
font-size: 64px;
line-height: 61.44px;
text-align: center;
color: #000;
max-width: 512px;
margin: 0 auto;
}
.walls_destroyed {
position: relative;
z-index: 3;
max-width: 512px;
margin: 22px auto;
text-align: center;
span {
color: var(--textmuted);
}
}
form {
z-index: 2;
position: relative;
.fields {
&::before {
content: '';
background-image: url(../assets/images/wall.png);
background-repeat: no-repeat;
background-size: 100% 100%;
width: 422px;
height: 306px;
position: absolute;
top: -110px;
right: -180px;
z-index: 1;
}
max-width: 470px;
margin: 0 auto;
position: relative;
.input {
position: relative;
z-index: 2;
padding-right: 28px;
padding-top: 2px;
.icon {
z-index: 2;
&--link {
position: absolute;
top: 50%;
left: 1rem;
margin-top: -6px;
}
}
input {
background-color: #F4F4F5;
padding: 16px 0 16px 44px;
border: 0;
border-radius: 8px;
width: 100%;
box-sizing: border-box;
position: relative;
line-height: 1.3em;
}
}
.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);
border-radius: 50%;
height: 56px;
width: 56px;
border: 0;
z-index: 3;
position: absolute;
top: 0;
right: 0;
cursor: pointer;
&:hover {
background-color: var(--marreta-darken);
}
.icon {
width: 23px;
height: 23px;
&--refresh,
&--marreta {
filter: invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%);
}
}
}
}
}
.adblock {
color: var(--textmuted);
font-size: 13px;
line-height: 1.2em;
text-align: center;
max-width: 470px;
position: relative;
z-index: 3;
margin: 22px auto 0 auto;
}
.plus {
z-index: 3;
position: relative;
background-color: rgba(244, 244, 245, 1);
margin-left: calc(-1*var(--container_spacing));
margin-right: calc(-1*var(--container_spacing));
@include mixin.devices(desktop) {
background-color: transparent;
display: grid;
grid-auto-columns: 1fr;
grid-template-columns: 1fr 1fr;
gap: 0px 38px;
align-items: start;
max-width: 900px;
margin: 62px auto 0 auto;
}
h2 {
font-size: 16px;
padding-bottom: 8px;
margin: 0;
.icon {
margin-right: 10px;
}
}
.text {
font-size: 14px;
color: var(--textmuted);
padding-left: 26px;
ol {
padding-left: 16px;
margin: 0;
}
p {
margin: 0;
padding-right: 22px;
}
strong {
font-weight: 600;
color: var(--text);
}
}
.add_as_app {
margin-top: 62px;
padding: var(--container_spacing);
@include mixin.devices(desktop) {
padding: 0;
margin-top: 0;
}
}
.bookmarklet {
display: none;
@include mixin.devices(desktop) {
display: block;
}
a {
border: 2px solid var(--marreta);
color: var(--marreta);
border-radius: 40px;
padding: 8px 16px;
margin-top: 16px;
display: inline-block;
font-weight: 600;
&:hover {
border-color: var(--marreta-darken);
color: var(--marreta-darken);
}
}
}
}
}
footer {}

View file

@ -0,0 +1,10 @@
@forward "normalize.css/normalize";
@use "mixin";
@use "fonts";
@use "root";
@use "base";
@use "icons";
@use "home";

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#3B82F6" d="M32 32l448 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96L0 64C0 46.3 14.3 32 32 32zm0 128l448 0 0 256c0 35.3-28.7 64-64 64L96 480c-35.3 0-64-28.7-64-64l0-256zm128 80c0 8.8 7.2 16 16 16l160 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-160 0c-8.8 0-16 7.2-16 16z"/></svg>

Before

(image error) Size: 378 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#FBBF24" d="M0 48C0 21.5 21.5 0 48 0l0 48V441.4l130.1-92.9c8.3-6 19.6-6 27.9 0L336 441.4V48H48V0H336c26.5 0 48 21.5 48 48V488c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5 37.9 507.5c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>

Before

(image error) Size: 311 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#10B981" d="M240 32a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM192 48a32 32 0 1 1 0 64 32 32 0 1 1 0-64zm-32 80c17.7 0 32 14.3 32 32l8 0c13.3 0 24 10.7 24 24l0 16c0 1.7-.2 3.4-.5 5.1C280.3 229.6 320 286.2 320 352c0 88.4-71.6 160-160 160S0 440.4 0 352c0-65.8 39.7-122.4 96.5-146.9c-.4-1.6-.5-3.3-.5-5.1l0-16c0-13.3 10.7-24 24-24l8 0c0-17.7 14.3-32 32-32zm0 320a96 96 0 1 0 0-192 96 96 0 1 0 0 192zm192-96c0-25.9-5.1-50.5-14.4-73.1c16.9-32.9 44.8-59.1 78.9-73.9c-.4-1.6-.5-3.3-.5-5.1l0-16c0-13.3 10.7-24 24-24l8 0c0-17.7 14.3-32 32-32s32 14.3 32 32l8 0c13.3 0 24 10.7 24 24l0 16c0 1.7-.2 3.4-.5 5.1C600.3 229.6 640 286.2 640 352c0 88.4-71.6 160-160 160c-62 0-115.8-35.3-142.4-86.9c9.3-22.5 14.4-47.2 14.4-73.1zm224 0a96 96 0 1 0 -192 0 96 96 0 1 0 192 0zM368 0a32 32 0 1 1 0 64 32 32 0 1 1 0-64zm80 48a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

Before

(image error) Size: 910 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#6B7280" d="M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"/></svg>

Before

(image error) Size: 558 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#EF4444" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-80c-13.3 0-24 10.7-24 24s10.7 24 24 24zm-24-72h128c13.3 0 24-10.7 24-24s-10.7-24-24-24H192c-13.3 0-24 10.7-24 24s10.7 24 24 24z"/></svg>

Before

(image error) Size: 321 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#3B82F6" d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>

Before

(image error) Size: 874 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#3B82F6" d="M283.9 378.6l18.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.9s0 0 0 0L122 208.8s0 0 0 0L17.7 156.6C1.9 148.7-4.5 129.5 3.4 113.7l40-80C48.8 22.8 59.9 16 72 16l120 0c5 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.6zM107.6 237.4l85.9 42.9L90.9 485.5c-11.9 23.7-40.7 33.3-64.4 21.5S-6.8 466.2 5.1 442.5L107.6 237.4z"/></svg>

Before

(image error) Size: 840 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#3B82F6" d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 0 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c0 0 0 0 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.8c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/></svg>

Before

(image error) Size: 875 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFFFFF" d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>

Before

(image error) Size: 328 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FBBF24" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>

Before

(image error) Size: 399 B

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

@ -1,84 +1,52 @@
<?php
/**
* Main configuration file
* Arquivo de configuração principal
*
* This file contains all global system settings, including:
* Este arquivo contém todas as configurações globais do sistema, incluindo:
*
* - Environment variables loading / Carregamento de variáveis de ambiente
* - System constants definition / Definições de constantes do sistema
* - Security settings / Configurações de segurança
* - Bot and user agent settings / Configurações de bots e user agents
* - Blocked domains list / Lista de domínios bloqueados
* - S3 cache settings / Configurações de cache S3
* System configuration manager
* - Loads and validates environment variables
* - Defines global constants for system settings
* - Manages security rules and external service configs
*/
require_once __DIR__ . '/vendor/autoload.php';
try {
// Initialize environment variables
// Inicializa as variáveis de ambiente
// Load environment variables
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
// Validate required fields
// Valida campos obrigatórios
$dotenv->required([
'SITE_NAME',
'SITE_DESCRIPTION',
'SITE_URL'
])->notEmpty();
// Custom URL validation
// Validação personalizada de URL
// Validate URL format
if (!filter_var($_ENV['SITE_URL'], FILTER_VALIDATE_URL)) {
throw new Exception('SITE_URL must be a valid URL');
}
/**
* Basic system settings
* Configurações básicas do sistema
*/
// Core system settings
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 default values
// Configurações opcionais com valores padrão
// Optional settings with defaults
define('DNS_SERVERS', $_ENV['DNS_SERVERS'] ?? '1.1.1.1, 8.8.8.8');
define('DISABLE_CACHE', isset($_ENV['DISABLE_CACHE']) ?
filter_var($_ENV['DISABLE_CACHE'], FILTER_VALIDATE_BOOLEAN) : false);
define('DISABLE_CACHE', isset($_ENV['DISABLE_CACHE']) ? filter_var($_ENV['DISABLE_CACHE'], FILTER_VALIDATE_BOOLEAN) : false);
define('SELENIUM_HOST', $_ENV['SELENIUM_HOST'] ?? 'localhost:4444');
define('CACHE_DIR', __DIR__ . '/cache');
define('LANGUAGE', $_ENV['LANGUAGE'] ?? 'pt-br');
/**
* Redis settings
* Configurações do Redis
*/
define('REDIS_HOST', $_ENV['REDIS_HOST'] ?? 'localhost');
define('REDIS_PORT', $_ENV['REDIS_PORT'] ?? 6379);
define('REDIS_PREFIX', $_ENV['REDIS_PREFIX'] ?? 'marreta:');
/**
* Logging settings
* Configurações de log
*/
define('LOG_LEVEL', $_ENV['LOG_LEVEL'] ?? 'WARNING'); // Available: DEBUG, INFO, WARNING, ERROR, CRITICAL
// Logging configuration
define('LOG_LEVEL', $_ENV['LOG_LEVEL'] ?? 'WARNING'); // DEBUG, INFO, WARNING, ERROR, CRITICAL
define('LOG_DAYS_TO_KEEP', 7);
/**
* S3 Cache settings
* Configurações de Cache S3
*/
define('S3_CACHE_ENABLED', isset($_ENV['S3_CACHE_ENABLED']) ?
filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN) : false);
// S3 cache configuration
define('S3_CACHE_ENABLED', isset($_ENV['S3_CACHE_ENABLED']) ? filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN) : false);
if (S3_CACHE_ENABLED) {
// Validate required S3 settings when S3 cache is enabled
// Valida configurações obrigatórias do S3 quando o cache S3 está ativado
$dotenv->required([
'S3_ACCESS_KEY',
'S3_SECRET_KEY',
@ -94,10 +62,7 @@ try {
define('S3_ENDPOINT', $_ENV['S3_ENDPOINT'] ?? null);
}
/**
* Load system configurations
* Carrega as configurações do sistema
*/
// Load security rules
define('BLOCKED_DOMAINS', require __DIR__ . '/data/blocked_domains.php');
define('DOMAIN_RULES', require __DIR__ . '/data/domain_rules.php');
define('GLOBAL_RULES', require __DIR__ . '/data/global_rules.php');

View file

@ -2,17 +2,13 @@
/**
* List of blocked domains
* Lista de domínios bloqueados
*
* Defines domains that cannot be accessed by the system
* due to usage policies or technical restrictions
*
* Define os domínios que não podem ser acessados pelo sistema
* por questões de política de uso ou restrições técnicas
*/
return [
// News sites / Sites de notícias
//-- Content behind login access / Conteúdo fica atrás de um acesso de login
// News sites
//-- Content behind login access/hard paywall
'wsj.com',
'piaui.folha.uol.com.br',
'economist.com',
@ -31,14 +27,16 @@ return [
'mittelbayerische.de',
'josimarfootball.com',
'nordsee-zeitung.de',
//-- Technical access blocking / Bloqueio técnico de acesso ao conteúdo
'zorgvisie.nl',
// List of common blocked sites to avoid unnecessary requests
//-- Technical access blocking
'bloomberg.com',
'sportskeeda.com',
'kansascity.com',
'fastcompany.com',
'expressnews.com',
'nydailynews.com',
// Tracking services / Serviços de rastreamento
//-- Tracking services
'metaffiliation.com',
'google-analytics.com',
'googletagmanager.com',
@ -59,7 +57,7 @@ return [
'fullstory.com',
'heap.io',
'clearbrain.com',
// Social networks / Redes sociais
//-- Social networks
'facebook.com',
'instagram.com',
'twitter.com',
@ -73,7 +71,7 @@ return [
'redd.it',
'bsky.app',
'threads.net',
// Streaming services / Serviços de streaming
//-- Streaming services
'netflix.com',
'hulu.com',
'disneyplus.com',
@ -81,32 +79,32 @@ return [
'spotify.com',
'youtube.com',
'twitch.tv',
// E-commerce sites / Sites de comércio eletrônico
//-- E-commerce sites
'amazon.com',
'ebay.com',
'aliexpress.com',
'mercadolivre.com.br',
'shopify.com',
// File sharing / Compartilhamento de arquivos
//-- File sharing
'mega.nz',
'mediafire.com',
'wetransfer.com',
'dropbox.com',
'torrent9.pe',
'thepiratebay.org',
// Adult sites / Sites adultos
//-- Adult sites
'pornhub.com',
'xvideos.com',
'xnxx.com',
'onlyfans.com',
'privacy.com.br',
'fatalmodel.com',
// Betting and gaming / Apostas e jogos
//-- Betting and gaming
'bet365.com',
'betfair.com',
'pokerstars.com',
'casino.com',
// Other popular sites / Outros sites populares
//-- Other popular sites
'github.com',
'stackoverflow.com',
'wikipedia.org',

View file

@ -2,38 +2,31 @@
/**
* Specific rule configurations for individual domains
* Configurações específicas de regras para domínios individuais
*
* This file contains custom rules for specific sites, allowing
* system behavior adjustment for each domain individually.
*
* Este arquivo contém regras personalizadas para sites específicos, permitindo
* ajustar o comportamento do sistema para cada domínio individualmente.
*
* Domain rule structure / Estrutura das regras por domínio:
* - userAgent: Define custom User-Agent for the domain / Define um User-Agent personalizado para o domínio
* - headers: Custom HTTP headers for requests / Headers HTTP personalizados para requisições
* - idElementRemove: Array of HTML IDs to be removed / Array de IDs HTML que devem ser removidos da página
* - classElementRemove: Array of HTML classes to be removed / Array de classes HTML que devem ser removidas
* - scriptTagRemove: Array of scripts to be removed (partial match) / Array de scripts que devem ser removidos (partial match)
* - cookies: Associative array of cookies to be set (null removes cookie) / Array associativo de cookies a serem definidos (null remove o cookie)
* - classAttrRemove: Array of classes to be removed from elements / Array de classes a serem removidas de elementos
* - customCode: String containing custom JavaScript code / String contendo código JavaScript personalizado
* - customStyle: String containing custom CSS code / String contendo código CSS personalizado
* - excludeGlobalRules: Associative array of global rules to exclude for this domain / Array associativo de regras globais a serem excluídas para este domínio
* Example / Exemplo:
* - userAgent: Define custom User-Agent for the domain
* - headers: Custom HTTP headers for requests
* - idElementRemove: Array of HTML IDs to be removed
* - classElementRemove: Array of HTML classes to be removed
* - scriptTagRemove: Array of scripts to be removed (partial match)
* - cookies: Associative array of cookies to be set (null removes cookie)
* - classAttrRemove: Array of classes to be removed from elements
* - customCode: String containing custom JavaScript code
* - customStyle: String containing custom CSS code
* - excludeGlobalRules: Associative array of global rules to exclude for this domain
* Example:
* 'excludeGlobalRules' => [
* 'scriptTagRemove' => ['gtm.js', 'ga.js'], // Excludes specific scripts from global rules / Exclui scripts específicos das regras globais
* 'classElementRemove' => ['subscription'] // Excludes specific classes from global rules / Exclui classes específicas das regras globais
* 'scriptTagRemove' => ['gtm.js', 'ga.js'],
* 'classElementRemove' => ['subscription']
* ]
* - fetchStrategies: String indicating which fetch strategy to use. Available values: / String indicando qual estratégia de fetch usar. Valores disponíveis:
* - fetchContent: Use standard fetch with domain rules / Usa fetch padrão com regras do domínio
* - fetchFromWaybackMachine: Try to fetch from Internet Archive / Tenta buscar do Internet Archive
* - fetchFromSelenium: Use Selenium for extraction / Usa Selenium para extração
* - socialReferrers: Add random social media headers / Adiciona headers randomicos de redes sociais
* - fromGoogleBot: Adds simulation of request coming from Google Bot / Adiciona simulação de requisição vinda do Google Bot
* - removeElementsByTag: Remove specific elements via DOM / Remove elementos especificos via DOM
* - removeCustomAttr: Remove custom attributes from elements / Remove custom attributes from elements
* - fetchStrategies: String indicating which fetch strategy to use. Available values:
* - fetchContent: Use standard fetch with domain rules
* - fetchFromWaybackMachine: Try to fetch from Internet Archive
* - fetchFromSelenium: Use Selenium for extraction
* - socialReferrers: Add random social media headers
* - fromGoogleBot: Adds simulation of request coming from Google Bot
* - removeElementsByTag: Remove specific elements via DOM
* - removeCustomAttr: Remove custom attributes from elements
*/
return [
'nsctotal.com.br' => [
@ -47,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'],
@ -71,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',
@ -137,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'],
@ -159,7 +185,8 @@ return [
]
],
'theverge.com' => [
'fetchStrategies' => 'fetchFromSelenium',
'scriptTagRemove' => 'zephr',
'classElementRemove' => 'zephr'
],
'economist.com' => [
'cookies' => [
@ -186,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

@ -2,23 +2,12 @@
/**
* Global rule configurations applied to all domains
* Configurações globais de regras aplicadas a todos os domínios
*
* This file defines rules that are applied by default to all sites,
* organized into categories for better maintenance and understanding.
*
* Este arquivo define regras que são aplicadas por padrão a todos os sites,
* organizadas em categorias para melhor manutenção e compreensão.
*
* Note: These rules can be overridden or disabled for specific domains
* using the 'excludeGlobalRules' configuration in domain_rules.php
*
* Nota: Estas regras podem ser sobrescritas ou desativadas para domínios específicos
* usando a configuração 'excludeGlobalRules' em domain_rules.php
*/
return [
// HTML classes to be removed from all pages
// Classes HTML a serem removidas de todas as páginas
// Classes to be removed from all pages:
'classElementRemove' => [
'subscription',
'subscriber-content',
@ -40,8 +29,7 @@ return [
'signup-overlay',
'onesignal-slidedown-container'
],
// Scripts to be removed from all pages
// Scripts a serem removidos de todas as páginas
// Scripts to be removed from all pages:
'scriptTagRemove' => [
'gtm.js',
'ga.js',
@ -80,6 +68,8 @@ return [
'getblue.io',
'smartocto.com',
'cdn.pn.vg',
'static.vocstatic.com'
'static.vocstatic.com',
'recaptcha',
'intercom'
]
];

2
app/dist/css/style.css vendored Normal file

File diff suppressed because one or more lines are too long

1
app/dist/css/style.css.map vendored Normal file

File diff suppressed because one or more lines are too long

BIN
app/dist/fonts/inter-500.eot vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/inter-500.ttf vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/inter-500.woff vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/inter-500.woff2 vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/inter-600.eot vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/inter-600.ttf vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/inter-600.woff vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/inter-600.woff2 vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/unna-400.eot vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/unna-400.ttf vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/unna-400.woff vendored Normal file

Binary file not shown.

BIN
app/dist/fonts/unna-400.woff2 vendored Normal file

Binary file not shown.

1
app/dist/icons/android.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-android2"><path d="m10.213 1.471.691-1.26q.069-.124-.048-.192-.128-.057-.195.058l-.7 1.27A4.8 4.8 0 0 0 8.005.941q-1.032 0-1.956.404l-.7-1.27Q5.281-.037 5.154.02q-.117.069-.049.193l.691 1.259a4.25 4.25 0 0 0-1.673 1.476A3.7 3.7 0 0 0 3.5 5.02h9q0-1.125-.623-2.072a4.27 4.27 0 0 0-1.664-1.476ZM6.22 3.303a.37.37 0 0 1-.267.11.35.35 0 0 1-.263-.11.37.37 0 0 1-.107-.264.37.37 0 0 1 .107-.265.35.35 0 0 1 .263-.11q.155 0 .267.11a.36.36 0 0 1 .112.265.36.36 0 0 1-.112.264m4.101 0a.35.35 0 0 1-.262.11.37.37 0 0 1-.268-.11.36.36 0 0 1-.112-.264q0-.154.112-.265a.37.37 0 0 1 .268-.11q.155 0 .262.11a.37.37 0 0 1 .107.265q0 .153-.107.264M3.5 11.77q0 .441.311.75.311.306.76.307h.758l.01 2.182q0 .414.292.703a.96.96 0 0 0 .7.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h1.343v2.182q0 .414.292.703a.97.97 0 0 0 .71.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h.76q.436 0 .749-.308.31-.307.311-.75V5.365h-9zm10.495-6.587a.98.98 0 0 0-.702.278.9.9 0 0 0-.293.685v4.063q0 .406.293.69a.97.97 0 0 0 .702.284q.42 0 .712-.284a.92.92 0 0 0 .293-.69V6.146a.9.9 0 0 0-.293-.685 1 1 0 0 0-.712-.278m-12.702.283a1 1 0 0 1 .712-.283q.41 0 .702.283a.9.9 0 0 1 .293.68v4.063a.93.93 0 0 1-.288.69.97.97 0 0 1-.707.284 1 1 0 0 1-.712-.284.92.92 0 0 1-.293-.69V6.146q0-.396.293-.68"/></svg>

After

(image error) Size: 1.3 KiB

1
app/dist/icons/apple.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple"><path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/><path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/></svg>

After

(image error) Size: 1.4 KiB

1
app/dist/icons/bookmark.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M0 48C0 21.5 21.5 0 48 0v441.4l130.1-92.9c8.3-6 19.6-6 27.9 0l130 92.9V48H48V0h288c26.5 0 48 21.5 48 48v440c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5l-154.1 110c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>

After

(image error) Size: 289 B

1
app/dist/icons/bsky.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 568 501"><path fill="currentColor" d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748 114.875 19.55 144.097 84.31 80.986 149.07-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"/></svg>

After

(image error) Size: 668 B

1
app/dist/icons/chrome.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-chrome"><path fill-rule="evenodd" d="M16 8a8 8 0 0 1-7.022 7.94l1.902-7.098a3 3 0 0 0 .05-1.492A3 3 0 0 0 10.237 6h5.511A8 8 0 0 1 16 8M0 8a8 8 0 0 0 7.927 8l1.426-5.321a3 3 0 0 1-.723.255 3 3 0 0 1-1.743-.147 3 3 0 0 1-1.043-.7L.633 4.876A8 8 0 0 0 0 8m5.004-.167L1.108 3.936A8.003 8.003 0 0 1 15.418 5H8.066a3 3 0 0 0-1.252.243 2.99 2.99 0 0 0-1.81 2.59M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4"/></svg>

After

(image error) Size: 501 B

1
app/dist/icons/close.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg"><path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/></svg>

After

(image error) Size: 284 B

1
app/dist/icons/error.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm-40-176h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-80c-13.3 0-24 10.7-24 24s10.7 24 24 24zm-24-72h128c13.3 0 24-10.7 24-24s-10.7-24-24-24H192c-13.3 0-24 10.7-24 24s10.7 24 24 24z"/></svg>

After

(image error) Size: 305 B

1
app/dist/icons/firefox.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-firefox"><path d="M13.384 3.408c.535.276 1.22 1.152 1.556 1.963a8 8 0 0 1 .503 3.897l-.009.077-.026.224A7.758 7.758 0 0 1 .006 8.257v-.04q.025-.545.114-1.082c.01-.074.075-.42.09-.489l.01-.051a6.6 6.6 0 0 1 1.041-2.35q.327-.465.725-.87.35-.358.758-.65a1.5 1.5 0 0 1 .26-.137c-.018.268-.04 1.553.268 1.943h.003a5.7 5.7 0 0 1 1.868-1.443 3.6 3.6 0 0 0 .021 1.896q.105.07.2.152c.107.09.226.207.454.433l.068.066.009.009a2 2 0 0 0 .213.18c.383.287.943.563 1.306.741.201.1.342.168.359.193l.004.008c-.012.193-.695.858-.933.858-2.206 0-2.564 1.335-2.564 1.335.087.997.714 1.839 1.517 2.357a4 4 0 0 0 .439.241q.114.05.228.094c.325.115.665.18 1.01.194 3.043.143 4.155-2.804 3.129-4.745v-.001a3 3 0 0 0-.731-.9 3 3 0 0 0-.571-.37l-.003-.002a2.68 2.68 0 0 1 1.87.454 3.92 3.92 0 0 0-3.396-1.983q-.116.001-.23.01l-.042.003V4.31h-.002a4 4 0 0 0-.8.14 7 7 0 0 0-.333-.314 2 2 0 0 0-.2-.152 4 4 0 0 1-.088-.383 5 5 0 0 1 1.352-.289l.05-.003c.052-.004.125-.01.205-.012C7.996 2.212 8.733.843 10.17.002l-.003.005.003-.001.002-.002h.002l.002-.002h.015a.02.02 0 0 1 .012.007 2.4 2.4 0 0 0 .206.48q.09.153.183.297c.49.774 1.023 1.379 1.543 1.968.771.874 1.512 1.715 2.036 3.02l-.001-.013a8 8 0 0 0-.786-2.353"/></svg>

After

(image error) Size: 1.3 KiB

1
app/dist/icons/hamburguer.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list"><path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5"/></svg>

After

(image error) Size: 317 B

1
app/dist/icons/link.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6 31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0l112.3-112.3zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5 50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5l112.2-112.3c31.5-31.5 82.5-31.5 114 0 27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>

After

(image error) Size: 856 B

1
app/dist/icons/marreta.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><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>

After

(image error) Size: 805 B

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

1
app/dist/icons/refresh.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8 62.5-62.5 163.8-62.5 226.3 0l17.1 17.2H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h111.9c17.7 0 32-14.3 32-32V80c0-17.7-14.3-32-32-32s-32 14.3-32 32v35.2l-17.5-17.6c-87.5-87.5-229.3-87.5-316.8 0-24.4 24.4-42 53.1-52.8 83.8-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2-4 4-6.7 8.8-8.1 14-.3 1.2-.6 2.5-.8 3.8-.3 1.7-.4 3.4-.4 5.1V432c0 17.7 14.3 32 32 32s32-14.3 32-32v-35.1l17.6 17.5c87.5 87.4 229.3 87.4 316.7 0 24.4-24.4 42.1-53.1 52.9-83.8 5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8-62.5 62.5-163.8 62.5-226.3 0l-.1-.1-17.1-17H160c17.7 0 32-14.3 32-32s-14.3-32-32-32H48.4c-1.6 0-3.2.1-4.8.3s-3.1.5-4.6 1z"/></svg>

After

(image error) Size: 796 B

1
app/dist/icons/telegram.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-telegram"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.287 5.906q-1.168.486-4.666 2.01-.567.225-.595.442c-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294q.39.01.868-.32 3.269-2.206 3.374-2.23c.05-.012.12-.026.166.016s.042.12.037.141c-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8 8 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629q.14.092.27.187c.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.4 1.4 0 0 0-.013-.315.34.34 0 0 0-.114-.217.53.53 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09"/></svg>

After

(image error) Size: 687 B

1
app/dist/icons/warning.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7.2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8.2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24v112c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0-64 0 32 32 0 1 0 64 0z"/></svg>

After

(image error) Size: 380 B

BIN
app/dist/images/opengraph.png vendored Normal file

Binary file not shown.

After

(image error) Size: 6.4 KiB

BIN
app/dist/images/opengraph.webp vendored Normal file

Binary file not shown.

After

(image error) Size: 5.9 KiB

BIN
app/dist/images/pwa/192x192.png vendored Normal file

Binary file not shown.

After

(image error) Size: 2.2 KiB

BIN
app/dist/images/pwa/192x192.webp vendored Normal file

Binary file not shown.

After

(image error) Size: 1.4 KiB

BIN
app/dist/images/pwa/512x512.png vendored Normal file

Binary file not shown.

After

(image error) Size: 6.9 KiB

BIN
app/dist/images/pwa/512x512.webp vendored Normal file

Binary file not shown.

After

(image error) Size: 3.9 KiB

BIN
app/dist/images/wall.png vendored Normal file

Binary file not shown.

After

(image error) Size: 45 KiB

BIN
app/dist/images/wall.webp vendored Normal file

Binary file not shown.

After

(image error) Size: 4.5 KiB

2
app/dist/js/scripts.js vendored Normal file
View file

@ -0,0 +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 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

1
app/dist/js/scripts.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,53 +1,34 @@
<?php
namespace Inc;
use Inc\Cache\CacheStorageInterface;
use Inc\Cache\DiskStorage;
use Inc\Cache\S3Storage;
use Inc\Cache\RedisStorage;
use Inc\Cache\SQLiteStorage;
/**
* Class responsible for system cache management
* Classe responsável pelo gerenciamento de cache do sistema
*
* This class implements functionalities to store and retrieve
* cached content, supporting multiple storage backends (disk or S3).
* The cache is organized by URLs converted to unique IDs using SHA-256.
* Content is compressed using gzip to save space.
*
* Esta classe implementa funcionalidades para armazenar e recuperar
* conteúdo em cache, suportando múltiplos backends de armazenamento (disco ou S3).
* O cache é organizado por URLs convertidas em IDs únicos usando SHA-256.
* O conteúdo é comprimido usando gzip para economizar espaço.
* System cache management with multiple storage backends (disk/S3)
* Uses SHA-256 hashed URLs as unique identifiers
* Implements gzip compression for space efficiency
*/
class Cache
{
/**
* @var CacheStorageInterface Storage implementation for cache
* @var CacheStorageInterface Implementação de storage para o cache
*/
/** @var CacheStorageInterface Cache storage implementation */
private $storage;
/**
* @var RedisStorage Redis instance for file counting
* @var RedisStorage Instância do Redis para contagem de arquivos
*/
private $redisStorage;
/** @var SQLiteStorage SQLite instance for file counting */
private $sqliteStorage;
/**
* Class constructor
* Construtor da classe
*
* Initializes appropriate storage based on configuration
* Inicializa o storage apropriado baseado na configuração
* Initializes storage based on configuration
* Uses S3Storage if configured and enabled
* Defaults to SQLiteStorage otherwise (which delegates to DiskStorage)
*/
public function __construct()
{
// Initialize RedisStorage for file counting
// Inicializa o RedisStorage para contagem de arquivos
$this->redisStorage = new RedisStorage(CACHE_DIR);
// If S3 is configured and active, use S3Storage
// Se S3 está configurado e ativo, usa S3Storage
$this->sqliteStorage = new SQLiteStorage(CACHE_DIR);
if (defined('S3_CACHE_ENABLED') && S3_CACHE_ENABLED === true) {
$this->storage = new S3Storage([
'key' => S3_ACCESS_KEY,
@ -59,51 +40,30 @@ class Cache
'endpoint' => defined('S3_ENDPOINT') ? S3_ENDPOINT : null
]);
} else {
// Otherwise, use disk storage
// Caso contrário, usa o storage em disco
$this->storage = new DiskStorage(CACHE_DIR);
$this->storage = $this->sqliteStorage;
}
}
/**
* Gets the count of cached files
* Obtém a contagem de arquivos em cache
*
* @return int Number of files in cache / Número de arquivos em cache
*/
/** Gets total number of cached files */
public function getCacheFileCount(): int
{
return $this->redisStorage->countCacheFiles();
return $this->sqliteStorage->countCacheFiles();
}
/**
* Generates a unique ID for a URL
* Gera um ID único para uma URL
*
* @param string $url URL for which the ID will be generated / URL para qual será gerado o ID
* @return string SHA-256 hash of the normalized URL / Hash SHA-256 da URL normalizada
* Generates unique cache ID from URL
* Normalizes URL by removing protocol and www
* Returns SHA-256 hash of normalized URL
*/
public function generateId($url)
{
// Remove protocol and www
// Remove protocolo e www
$url = preg_replace('#^https?://(www\.)?#', '', $url);
// Generate unique ID using SHA-256
// Gera ID único usando SHA-256
return hash('sha256', $url);
}
/**
* Checks if cache exists for a given URL
* Verifica se existe cache para uma determinada URL
*
* @param string $url URL to check / URL a ser verificada
* @return bool True if cache exists, False otherwise / True se existir cache, False caso contrário
*/
/** Checks if cached version exists for URL */
public function exists($url)
{
// If DISABLE_CACHE is active, always return false
// Se DISABLE_CACHE está ativo, sempre retorna false
if (DISABLE_CACHE) {
return false;
}
@ -111,17 +71,9 @@ class Cache
return $this->storage->exists($this->generateId($url));
}
/**
* Retrieves cached content for a URL
* Recupera o conteúdo em cache de uma URL
*
* @param string $url URL of the content to retrieve / URL do conteúdo a ser recuperado
* @return string|null Cached content or null if it doesn't exist / Conteúdo em cache ou null se não existir
*/
/** Retrieves cached content for URL */
public function get($url)
{
// If DISABLE_CACHE is active, always return null
// Se DISABLE_CACHE está ativo, sempre retorna null
if (DISABLE_CACHE) {
return null;
}
@ -129,18 +81,9 @@ class Cache
return $this->storage->get($this->generateId($url));
}
/**
* Stores content in cache for a URL
* Armazena conteúdo em cache para uma URL
*
* @param string $url URL associated with the content / URL associada ao conteúdo
* @param string $content Content to be cached / Conteúdo a ser armazenado em cache
* @return bool True if cache was saved successfully, False otherwise / True se o cache foi salvo com sucesso, False caso contrário
*/
/** Stores content in cache for URL */
public function set($url, $content)
{
// If DISABLE_CACHE is active, don't generate cache
// Se DISABLE_CACHE está ativo, não gera cache
if (DISABLE_CACHE) {
return true;
}

View file

@ -3,39 +3,27 @@
namespace Inc\Cache;
/**
* Interface for cache storage implementations
* Interface para implementações de armazenamento de cache
*
* This interface defines the required methods for any cache storage implementation.
* Esta interface define os métodos necessários para qualquer implementação de armazenamento de cache.
* Defines the contract for cache storage implementations
*/
interface CacheStorageInterface
{
/**
* Checks if cache exists for a given ID
* Verifica se existe cache para um determinado ID
*
* @param string $id Cache ID / ID do cache
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
* Checks if cached content exists for given ID
* @param string $id Unique cache identifier
*/
public function exists(string $id): bool;
/**
* Retrieves cached content
* Recupera o conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
* Retrieves cached content by ID
* @return string|null Cached content or null if missing
*/
public function get(string $id): ?string;
/**
* Stores content in cache
* Armazena conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @param string $content Content to be stored / Conteúdo a ser armazenado
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
* Stores content in cache with specified ID
* @param string $id Cache entry identifier
* @param string $content Content to store
* @return bool Storage success status
*/
public function set(string $id, string $content): bool;
}
}

View file

@ -3,25 +3,19 @@
namespace Inc\Cache;
/**
* Disk-based cache storage implementation
* Implementação de armazenamento de cache em disco
*
* This class implements file-based caching using gzip compression
* Esta classe implementa cache baseado em arquivos usando compressão gzip
* Disk-based cache storage
* Implements file-based caching using gzip compression
*/
class DiskStorage implements CacheStorageInterface
{
/**
* @var string Directory where cache files will be stored
* @var string Diretório onde os arquivos de cache serão armazenados
* @var string Directory for cache files
*/
private $cacheDir;
/**
* Class constructor
* Construtor da classe
*
* @param string $cacheDir Base directory for cache storage / Diretório base para armazenamento do cache
* @param string $cacheDir Base directory for cache storage
*/
public function __construct(string $cacheDir)
{
@ -33,10 +27,8 @@ class DiskStorage implements CacheStorageInterface
/**
* Checks if cache exists for a given ID
* Verifica se existe cache para um determinado ID
*
* @param string $id Cache ID / ID do cache
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
* @param string $id Cache ID
* @return bool True if cache exists, false otherwise
*/
public function exists(string $id): bool
{
@ -46,10 +38,8 @@ class DiskStorage implements CacheStorageInterface
/**
* Retrieves cached content
* Recupera o conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
* @param string $id Cache ID
* @return string|null Cached content or null if not found
*/
public function get(string $id): ?string
{
@ -69,11 +59,9 @@ class DiskStorage implements CacheStorageInterface
/**
* Stores content in cache
* Armazena conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @param string $content Content to be stored / Conteúdo a ser armazenado
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
* @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
{
@ -86,4 +74,4 @@ class DiskStorage implements CacheStorageInterface
return file_put_contents($cachePath, $compressedContent) !== false;
}
}
}

View file

@ -1,171 +0,0 @@
<?php
namespace Inc\Cache;
use Redis;
/**
* Redis-based cache storage implementation
* Implementação de armazenamento de cache baseado em Redis
*
* This class provides cache storage and file counting functionality using Redis
* Esta classe fornece armazenamento de cache e funcionalidade de contagem de arquivos usando Redis
*
* @property \Redis|null $redis Redis client instance
*/
class RedisStorage implements CacheStorageInterface
{
/**
* @var \Redis|null Redis client instance
* @var \Redis|null Instância do cliente Redis
*/
private $redis;
/**
* @var string Cache directory for file counting
* @var string Diretório de cache para contagem de arquivos
*/
private $cacheDir;
/**
* Class constructor
* Construtor da classe
*
* @param string $cacheDir Base directory for cache storage / Diretório base para armazenamento do cache
*/
public function __construct(string $cacheDir)
{
$this->cacheDir = $cacheDir;
// Try to initialize Redis connection
// Tenta inicializar conexão Redis
try {
/** @var \Redis $redis */
$this->redis = new \Redis();
$this->redis->connect(REDIS_HOST, REDIS_PORT, 2.5);
$this->redis->setOption(\Redis::OPT_PREFIX, REDIS_PREFIX);
} catch (\Exception $e) {
// If it fails, set redis to null
// Se falhar, define redis como null
$this->redis = null;
}
}
/**
* Counts the number of files in the cache directory
* Conta o número de arquivos no diretório de cache
*
* @return int Number of files in the cache directory / Número de arquivos no diretório de cache
*/
public function countCacheFiles(): int
{
// Key to store file count in Redis
// Chave para armazenar a contagem de arquivos no Redis
$cacheCountKey = 'cache_file_count';
// If Redis is available
// Se Redis estiver disponível
if ($this->redis !== null) {
// Check if the key exists and has a value
// Verifica se a chave existe e tem valor
/** @var string|false $cachedCount */
$cachedCount = $this->redis->get($cacheCountKey);
if ($cachedCount !== false) {
return (int)$cachedCount;
}
}
// If Redis is not available or key is empty, count .gz files
// Se Redis não estiver disponível ou chave vazia, conta arquivos .gz
$fileCount = 0;
$iterator = new \FilesystemIterator($this->cacheDir);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'gz') {
$fileCount++;
}
}
// If Redis is available, save the count
// Se Redis estiver disponível, salva a contagem
if ($this->redis !== null) {
$this->redis->set($cacheCountKey, $fileCount);
}
return $fileCount;
}
/**
* Updates the file count in Redis
* Atualiza a contagem de arquivos no Redis
*
* @param int $count Number of files / Número de arquivos
*/
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
* Verifica se existe cache para um determinado ID
*
* @param string $id Cache ID / ID do cache
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
*/
public function exists(string $id): bool
{
return $this->redis !== null ? $this->redis->exists($id) : false;
}
/**
* Retrieves cached content
* Recupera o conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
*/
public function get(string $id): ?string
{
if ($this->redis === null) {
return null;
}
/** @var string|false $content */
$content = $this->redis->get($id);
return $content === false ? null : $content;
}
/**
* Stores content in cache
* Armazena conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @param string $content Content to be stored / Conteúdo a ser armazenado
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
*/
public function set(string $id, string $content): bool
{
// If Redis is not available, return false
// Se Redis não estiver disponível, retorna false
if ($this->redis === null) {
return false;
}
// When saving a new file, update the count
// Ao salvar um novo arquivo, atualiza a contagem
/** @var bool $result */
$result = $this->redis->set($id, $content);
if ($result) {
// Increment file count in Redis
// Incrementa a contagem de arquivos no Redis
/** @var string|false $currentCount */
$currentCount = $this->redis->get('cache_file_count') ?: 0;
$this->redis->set('cache_file_count', $currentCount + 1);
}
return $result;
}
}

View file

@ -7,42 +7,33 @@ use Aws\Exception\AwsException;
/**
* AWS S3-based cache storage implementation
* Implementação de armazenamento de cache baseado em AWS S3
*
* This class provides cache storage functionality using Amazon S3 or compatible services
* Esta classe fornece funcionalidade de armazenamento de cache usando Amazon S3 ou serviços compatíveis
* Provides cache storage functionality using Amazon S3 or compatible services
*/
class S3Storage implements CacheStorageInterface
{
/**
* @var S3Client AWS S3 Client
* @var S3Client Cliente AWS S3
*/
private $s3Client;
/**
* @var string S3 bucket name
* @var string Nome do bucket S3
*/
private $bucket;
/**
* @var string Prefix for objects in the bucket (optional)
* @var string Prefixo para os objetos no bucket (opcional)
*/
private $prefix;
/**
* @var string ACL for S3 objects
* @var string ACL para os objetos no S3
*/
private $acl;
/**
* Class constructor
* Construtor da classe
*
* @param array $config AWS S3 configuration / Configuração do AWS S3
* @param array $config AWS S3 configuration
*/
public function __construct(array $config)
{
@ -56,11 +47,8 @@ class S3Storage implements CacheStorageInterface
];
// Add custom endpoint if provided
// Adiciona endpoint personalizado se fornecido
if (!empty($config['endpoint'])) {
$clientConfig['endpoint'] = $config['endpoint'];
// Use path-style endpoints when a custom endpoint is provided
// Use endpoints estilo path quando um endpoint personalizado é fornecido
$clientConfig['use_path_style_endpoint'] = true;
}
@ -73,10 +61,8 @@ class S3Storage implements CacheStorageInterface
/**
* Generates the complete object key in S3
* Gera a chave completa do objeto no S3
*
* @param string $id Cache ID / ID do cache
* @return string Complete S3 object key / Chave completa do objeto no S3
* @param string $id Cache ID
* @return string Complete S3 object key
*/
private function getObjectKey(string $id): string
{
@ -85,10 +71,8 @@ class S3Storage implements CacheStorageInterface
/**
* Checks if cache exists for a given ID
* Verifica se existe cache para um determinado ID
*
* @param string $id Cache ID / ID do cache
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
* @param string $id Cache ID
* @return bool True if cache exists, false otherwise
*/
public function exists(string $id): bool
{
@ -98,17 +82,14 @@ class S3Storage implements CacheStorageInterface
$this->getObjectKey($id)
);
} catch (AwsException $e) {
// Log error if needed / Registra erro se necessário
return false;
}
}
/**
* Retrieves cached content
* Recupera o conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
* @param string $id Cache ID
* @return string|null Cached content or null if not found
*/
public function get(string $id): ?string
{
@ -136,11 +117,9 @@ class S3Storage implements CacheStorageInterface
/**
* Stores content in cache
* Armazena conteúdo em cache
*
* @param string $id Cache ID / ID do cache
* @param string $content Content to be stored / Conteúdo a ser armazenado
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
* @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
{
@ -156,13 +135,12 @@ class S3Storage implements CacheStorageInterface
'Body' => $compressedContent,
'ACL' => $this->acl,
'ContentEncoding' => 'gzip',
'CacheControl' => 'max-age=31536000' // 1 year / 1 ano
'CacheControl' => 'max-age=31536000' // 1 year
]);
return true;
} catch (AwsException $e) {
// Log error if needed / Registra erro se necessário
return false;
}
}
}
}

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

@ -1,27 +1,19 @@
<?php
namespace Inc;
/**
* Language Management Class
* Classe de Gerenciamento de Idiomas
*
* This class handles the loading and retrieval of language-specific strings
* Esta classe lida com o carregamento e recuperação de strings específicas do idioma
*
* Features / Funcionalidades:
* - Language initialization / Inicialização de idioma
* - Translation retrieval / Recuperação de traduções
* - Message handling / Manipulação de mensagens
* - Fallback language support / Suporte a idioma de fallback
* Manages language translations and localization
* Loads language files based on system configuration
* Provides fallback to default language on missing resources
*/
class Language {
private static $translations = [];
private static $currentLanguage = 'pt-br';
/**
* Initialize the language system
* Inicializa o sistema de idiomas
*
* @param string $language Language code (e.g., 'en', 'pt-br') / Código do idioma (ex: 'en', 'pt-br')
* Initializes language resources
* @param string $language ISO language code (e.g., 'en', 'pt-br')
*/
public static function init($language = 'pt-br') {
self::$currentLanguage = strtolower($language);
@ -30,31 +22,25 @@ class Language {
if (file_exists($langFile)) {
self::$translations = require $langFile;
} else {
// Fallback to pt-br if language file doesn't exist
// Volta para pt-br se o arquivo de idioma não existir
// Fallback to default language
self::$currentLanguage = 'pt-br';
self::$translations = require __DIR__ . '/../languages/pt-br.php';
}
}
/**
* Get a translation by key
* Obtém uma tradução por chave
*
* @param string $key Translation key / Chave da tradução
* @param string $default Default value if key not found / Valor padrão se a chave não for encontrada
* @return string Translation text / Texto traduzido
* Retrieves translation for specified key
* @param string $key Translation identifier
* @param string $default Fallback value if key not found
*/
public static function get($key, $default = '') {
return self::$translations[$key] ?? $default;
}
/**
* Get a message by key
* Obtém uma mensagem por chave
*
* @param string $key Message key / Chave da mensagem
* @return array Message data (message and type) / Dados da mensagem (mensagem e tipo)
* Gets structured message data
* @param string $key Message identifier
* @return array Message content and type
*/
public static function getMessage($key) {
return self::$translations['messages'][$key] ?? [
@ -63,13 +49,8 @@ class Language {
];
}
/**
* Get current language code
* Obtém o código do idioma atual
*
* @return string Current language code / Código do idioma atual
*/
/** Gets active language code */
public static function getCurrentLanguage() {
return self::$currentLanguage;
}
}
}

Some files were not shown because too many files have changed in this diff Show more