Compare commits

...

59 commits
1.19.2 ... main

Author SHA1 Message Date
Renan Bernordi
ef2827a6d2 add cnn selenium 2025-08-16 21:56:20 -03:00
Renan Bernordi
734acedecb fix validate url 2025-08-16 21:53:57 -03:00
Renan Bernordi
7c01bce35f darkmode 2025-07-17 00:54:38 -03:00
Renan Bernordi
bbcbdff8bc add 2025-07-17 00:33:54 -03:00
Renan Bernordi
33b437d8fe fix fetcj 2025-07-17 00:26:05 -03:00
Renan Bernordi
2071d5c2bc add restrict urls 2025-07-06 19:32:52 -03:00
Renan Bernordi
0a57629cff fix bin tasks 2025-07-06 19:25:01 -03:00
Renan Bernordi
4d458fb75f css fixes 2025-06-26 18:32:07 -03:00
Renan Bernordi
deea4d6a2a fixing cli commands 2025-06-26 18:12:51 -03:00
Renan Bernordi
22e836b707 add dmca domains block 2025-06-26 17:38:05 -03:00
Renan Bernordi
01237362c5 zh, teste 2025-05-30 01:00:38 -03:00
Renan Bernordi
08ba5eb1a6 stcatharinesstandard, primeiro teste proxy 2025-05-30 00:58:15 -03:00
Renan Bernordi
80a0bec993 ajuste wp 2025-05-30 00:53:39 -03:00
Renan Bernordi
86be4a69a5 rodar proxy list 2025-05-30 00:52:57 -03:00
Renan Bernordi
33a7569d17 ajuste no comando inicial 2025-05-30 00:46:29 -03:00
Renan Bernordi
3e99e34fa7 validação de regras e proxy 2025-05-27 23:20:22 -03:00
Renan Bernordi
b283965299 adicionado suporte a lista de proxy 2025-05-26 16:39:54 -03:00
Renan Bernordi
86e6c9b838 integração com regras do periscope 2025-05-26 13:15:08 -03:00
Renan Bernordi
99258b0376 nova regra de modificador de url 2025-05-26 13:14:55 -03:00
Renan Bernordi
ee6f57aa43 marreta recursiva #36 2025-05-02 10:36:30 -03:00
Renan Bernordi
5409407833 autofocus #34 2025-05-02 10:33:43 -03:00
Renan Bernordi
f09a861cd1 novas regras de dominios, issue #33 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 #24 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
132 changed files with 17401 additions and 2402 deletions

View file

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

51
.gitignore vendored
View file

@ -3,48 +3,23 @@ composer.lock
.env .env
app/logs/*.log app/logs/*.log
app/cache/*.gz app/cache/*.gz
app/cache/database/.sqlite
app/cache/*.json
TODO.md 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 composer.phar
/vendor/ /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* .fuse_hidden*
# KDE directory preferences
.directory .directory
# Linux trash folder which might appear on any partition or disk
.Trash-* .Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs* .nfs*
### macOS ###
# General
.DS_Store .DS_Store
.AppleDouble .AppleDouble
.LSOverride .LSOverride
# Icon must end with two \r
Icon Icon
# Thumbnails
._* ._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100 .DocumentRevisions-V100
.fseventsd .fseventsd
.Spotlight-V100 .Spotlight-V100
@ -52,42 +27,22 @@ Icon
.Trashes .Trashes
.VolumeIcon.icns .VolumeIcon.icns
.com.apple.timemachine.donotpresent .com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB .AppleDB
.AppleDesktop .AppleDesktop
Network Trash Folder Network Trash Folder
Temporary Items Temporary Items
.apdisk .apdisk
### macOS Patch ###
# iCloud generated files
*.icloud *.icloud
### Windows ###
# Windows thumbnail cache files
Thumbs.db Thumbs.db
Thumbs.db:encryptable Thumbs.db:encryptable
ehthumbs.db ehthumbs.db
ehthumbs_vista.db ehthumbs_vista.db
# Dump file
*.stackdump *.stackdump
# Folder config file
[Dd]esktop.ini [Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/ $RECYCLE.BIN/
# Windows Installer files
*.cab *.cab
*.msi *.msi
*.msix *.msix
*.msm *.msm
*.msp *.msp
# Windows shortcuts
*.lnk *.lnk
# End of https://www.toptal.com/developers/gitignore/api/composer,windows,macos,linux

View file

@ -1,50 +1,67 @@
FROM php:8.3-fpm # Stage 0: Base
FROM php:8.3-fpm AS base
# Install PHP dependencies and extensions # Install dependencies and extensions
# Instala dependências e extensões do PHP
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
nginx \ nginx \
nano \ nano \
procps \ procps \
psmisc \
zip \ zip \
git \ git \
htop \ htop \
cron \
libzip-dev \ libzip-dev \
libhiredis-dev \ libsqlite3-dev \
&& docker-php-ext-install zip opcache \ && docker-php-ext-install zip opcache pdo_sqlite \
&& pecl install redis \ && docker-php-ext-enable opcache \
&& docker-php-ext-enable redis opcache && apt-get clean && rm -rf /var/lib/apt/lists/*
# Stage 1: Build stage
FROM base AS builder
# Copy OPCache configuration # Copy OPCache configuration
# Copia a configuração do OPCache
COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini
# Install Composer # Install Composer
# Instala o Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Copy webservice configuration # Copy app folder
# Copia a configuração do webservice
COPY default.conf /etc/nginx/sites-available/default
RUN mkdir -p /app
COPY app/ /app/ COPY app/ /app/
# Install composer packages
WORKDIR /app WORKDIR /app
RUN composer install --no-interaction --optimize-autoloader RUN composer install --no-interaction --optimize-autoloader
# Stage 2: Final
FROM base
# 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 and configure initialization script permissions
# Copia e configura permissões do script de inicialização
COPY docker-entrypoint.sh /usr/local/bin/ COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /app/bin/cleanup
RUN chmod +x /app/bin/proxy
RUN mkdir -p /app/cache /app/logs # Create cache, database, and logs folders
RUN mkdir -p /app/cache /app/cache/database /app/logs
# Configure base permissions for /app directory # Configure base permissions for /app directory
# Configura permissões base para o diretório /app
RUN chown -R www-data:www-data /app \ RUN chown -R www-data:www-data /app \
&& chmod -R 755 /app && chmod -R 755 /app
# Configure Cron
RUN touch /app/logs/cron.log
RUN echo '0 * * * * root php "/app/bin/cleanup" >> /app/logs/cleanup.log 2>&1' >> /etc/crontab
RUN echo '0 * * * * root php "/app/bin/proxy" >> /app/logs/proxy.log 2>&1' >> /etc/crontab
EXPOSE 80 EXPOSE 80
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

View file

@ -1,49 +1,41 @@
# 🛠️ Marreta # 🛠️ 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) [![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) [![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) [![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) [![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)! 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 - Removes annoying tracking parameters
- Forces HTTPS to keep everything secure - Forces HTTPS to keep everything secure
- Changes user agent to avoid blocks - Changes user agent to avoid blocking
- Smart DNS - Leaves HTML clean and optimized
- Keeps HTML clean and optimized - Fixes relative URLs on its own
- Fixes relative URLs automatically - Allows you to add your own styles and scripts
- Allows custom styles
- Removes unwanted elements - Removes unwanted elements
- Cache, cache! - Caching, caching!
- Blocks domains you don't want - Blocks domains you don't want
- Allows custom headers and cookies configuration - Allows configuring headers and cookies your way
- Everything with SSL/TLS - PHP-FPM and OPcache
- PHP-FPM - Proxy Support
- OPcache enabled
- Direct sharing via PWA on Chrome on Android
## 🐳 Docker ## 🐳 Installing with Docker
### Prerequisites Install [Docker and Docker Compose](https://docs.docker.com/engine/install/)
You only need:
- Docker and docker compose
### Production
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml` `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` `nano docker-compose.yml`
@ -58,187 +50,49 @@ services:
- SITE_NAME= - SITE_NAME=
- SITE_DESCRIPTION= - SITE_DESCRIPTION=
- SITE_URL= - SITE_URL=
- LANGUAGE=
``` ```
- `SITE_NAME`: Your Marreta's name - `SITE_NAME`: Name of your Marreta
- `SITE_DESCRIPTION`: Tell what it's for - `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) - `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`: Server:PORT of Selenium host (e.g., selenium-hub:4444)
- `SELENIUM_HOST`: Selenium host server:PORT (e.g., selenium-hub:4444) - `LANGUAGE`: pt-br (Brazilian Portuguese), en (English), es (Spanish), de-de (German), ru-ru (Russian)
Now you can run `docker compose up -d` Now just run `docker compose up -d`
#### Development ### 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
1. First, clone the project:
```bash
git clone https://github.com/manualdousuario/marreta/
cd marreta
```
2. Create the configuration file: ### 🛡️ DMCA
```bash
cp app/.env.sample app/.env
```
3. Configure it your way in `app/.env`: To block domains from DMCA requests, create the file `app/cache/dmca_domains.json`:
```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: ```json
```bash [
docker-compose up -d {
``` "host": "exemplo.com.br",
"message": "This content has been blocked on request"
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/*
``` ```
## 🚀 Integrations ## 🚀 Integrations
- 🤖 **Telegram**: [Official Bot](https://t.me/leissoai_bot) - 🤖 **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) - 🦋 **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! 😉 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

200
README.md
View file

@ -1,7 +1,7 @@
# 🛠️ Marreta # 🛠️ 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) [![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) [![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) [![Stars](https://img.shields.io/github/stars/manualdousuario/marreta)](https://github.com/manualdousuario/marreta/stargazers)
@ -15,35 +15,28 @@ Instancia publica em [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
## ✨ O que tem de legal? ## ✨ O que tem de legal?
- Limpa e arruma URLs automaticamente - Limpa e corrige URLs automaticamente
- Remove parâmetros chatos de rastreamento - Remove parâmetros chatos de rastreamento
- Força HTTPS pra manter tudo seguro - Força HTTPS pra manter tudo seguro
- Troca de user agent pra evitar bloqueios - Troca de user agent pra evitar bloqueios
- DNS esperto
- Deixa o HTML limpinho e otimizado - Deixa o HTML limpinho e otimizado
- Conserta URLs relativas sozinho - Conserta URLs relativas sozinho
- Permite colocar seus próprios estilos - Permite colocar seus próprios estilos e scripts
- Remove elementos indesejados - Remove elementos indesejados
- Cache, cache! - Cache, cache!
- Bloqueia domínios que você não quer - Bloqueia domínios que você não quer
- Proteção DMCA com mensagens personalizadas
- Permite configurar headers e cookies do seu jeito - Permite configurar headers e cookies do seu jeito
- Tudo com SSL/TLS - PHP-FPM e OPcache
- PHP-FPM - Suporte a Proxy
- OPcache ligado
- Compartilhamento direto via PWA no Chrome do Android
## 🐳 Docker ## 🐳 Instalando em Docker
### Antes de começar Instale [Docker e Docker Compose](https://docs.docker.com/engine/install/)
Só precisa ter instalado:
- Docker e docker compose
### Produção
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml` `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` `nano docker-compose.yml`
@ -58,182 +51,43 @@ services:
- SITE_NAME= - SITE_NAME=
- SITE_DESCRIPTION= - SITE_DESCRIPTION=
- SITE_URL= - SITE_URL=
- LANGUAGE=
``` ```
- `SITE_NAME`: Nome do seu Marreta - `SITE_NAME`: Nome do seu Marreta
- `SITE_DESCRIPTION`: Conta pra que serve - `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) - `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) - `SELENIUM_HOST`: Servidor:PORTA do host do Selenium (ex: selenium-hub:4444)
- - `LANGUAGE`: pt-br (Português Brasil), en (Inglês), es (Espanhol) ou de-de (Alemão), ru-ru (Russo)
Agora pode rodar `docker compose up -d`
#### Desenvolvimento Agora só rodar `docker compose up -d`
1. Primeiro, clona o projeto: ### Mais configurações:
```bash - Selenium: https://github.com/manualdousuario/marreta/wiki/%F0%9F%92%BB-Selenium-Hub-(Chrome-and-Firefox)
git clone https://github.com/manualdousuario/marreta/ - Cache S3: https://github.com/manualdousuario/marreta/wiki/%F0%9F%97%83%EF%B8%8F-Cache-S3
cd marreta - Manutenção: https://github.com/manualdousuario/marreta/wiki/%F0%9F%9B%A0%EF%B8%8F-Maintenance
```
2. Cria o arquivo de configuração: ### 🛡️ DMCA
```bash
cp app/.env.sample app/.env
```
3. Configura do seu jeito no `app/.env`: Para bloquear dominios por pedidos de DMCA, crie o arquivo `app/cache/dmca_domains.json`:
```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: ```json
```bash [
docker-compose up -d {
``` "host": "exemplo.com.br",
"message": "Este conteúdo foi bloqueado a pedido"
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/*
``` ```
## 🚀 Integrações ## 🚀 Integrações
- 🤖 **Telegram**: [Bot oficial](https://t.me/leissoai_bot) - 🤖 **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) - 🦊 **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) - 🦋 **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) - 🍎 **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! 😉 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,32 +1,33 @@
# Arquivo de exemplo para configuração de variáveis de ambiente # Sample file for environment variable configuration
# Copie este arquivo para .env e ajuste os valores conforme necessário # Copy this file to .env and adjust the values as needed
# Nome do site exibido no cabeçalho e meta tags # Site name displayed in the header and meta tags
SITE_NAME=Marreta SITE_NAME=Marreta
# Descrição do site usada em meta tags e SEO # Site description used in meta tags and SEO
SITE_DESCRIPTION="Chapéu de paywall é marreta!" SITE_DESCRIPTION="Chapéu de paywall é marreta!"
# Idioma do site (opções disponíveis: pt-br, en, es, de-de) # Site language (available options: pt-br, en, es, de-de, ru-ru)
# pt-br = Português do Brasil # pt-br = Brazilian Portuguese
# en = English # en = English
# es = Español # es = Spanish
# de-de = German # de-de = German
# ru-ru = Russian
LANGUAGE=pt-br LANGUAGE=pt-br
# URL base do site (sem barra no final) # Base URL of the site (without a trailing slash)
# Use https://localhost para desenvolvimento local # Use https://localhost for local development
SITE_URL=https://localhost SITE_URL=https://localhost
# Lista de servidores DNS para resolução de domínios # List of DNS servers for domain resolution
# Recomendado: AdGuard DNS (94.140.14.14, 94.140.15.15) # Recommended: AdGuard DNS (94.140.14.14, 94.140.15.15)
DNS_SERVERS='94.140.14.14,94.140.15.15' DNS_SERVERS='94.140.14.14,94.140.15.15'
# Modo sem cache (true/false) # Disable cache mode (true/false)
# Quando ativo, desativa o cache do sistema # When enabled, system caching is turned off
DISABLE_CACHE=false DISABLE_CACHE=false
# Configurações de Cache S3 # S3 Cache Settings
S3_CACHE_ENABLED=false S3_CACHE_ENABLED=false
S3_ACCESS_KEY= S3_ACCESS_KEY=
S3_SECRET_KEY= S3_SECRET_KEY=
@ -36,8 +37,21 @@ S3_FOLDER=cache/
S3_ACL=private S3_ACL=private
S3_ENDPOINT= S3_ENDPOINT=
# Configurações do Selenium # Selenium Configuration
SELENIUM_HOST=localhost:4444 SELENIUM_HOST=localhost:4444
# Configurações de Debug # Debug Settings
DEBUG=true DEBUG=false
# Cache Cleanup Settings
# Number of days to keep cache files (*.gz)
# If not set, no files will be cleaned
CLEANUP_DAYS=7
# Proxy List Configuration
# URL to download proxy list from (used by bin/proxy script)
# The proxy list should contain proxies in one of these formats:
# 1. http://USER:PASSWORD@HOST:PORT
# 2. IP:PORT:USER:PASSWORD
# Example: PROXY_LIST=https://example.com/proxy-list.txt
PROXY_LIST=

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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 838 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-fill" viewBox="0 0 16 16">
<path d="M6 .278a.77.77 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278"/>
</svg>

After

Width:  |  Height:  |  Size: 394 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

Width:  |  Height:  |  Size: 873 B

3
app/assets/icons/sun.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-brightness-high-fill" viewBox="0 0 16 16">
<path d="M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708"/>
</svg>

After

Width:  |  Height:  |  Size: 791 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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 397 B

View file

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -1,130 +1,120 @@
/**
* 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 * Service Worker registration for PWA functionality
* Registers a service worker to enable offline capabilities and PWA features * 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) { if ('serviceWorker' in navigator) {
window.addEventListener('load', () => { window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js') navigator.serviceWorker.register('/service-worker.js')
.then(() => { .then(() => {
// Service Worker registered successfully // Service Worker registered successfully
// Service Worker registrado com sucesso
}) })
.catch(() => { .catch(() => {
// Service Worker registration failed // 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);
}
});
}
// Dark mode
const themeToggle = document.getElementById('themeToggle');
const html = document.documentElement;
const savedTheme = localStorage.getItem('theme') || 'light';
html.setAttribute('data-theme', savedTheme);
if (themeToggle) {
themeToggle.addEventListener('click', () => {
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
});

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,46 @@
@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%)');
@include mixin.icon('sun', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
@include mixin.icon('moon', '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,84 @@
@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%;
/* Light theme colors */
@include mixin.create-color('marreta', #3B82F6);
@include mixin.create-color('text', #484848);
@include mixin.create-color('textmuted', #818181);
@include mixin.create-color('link', #3B82F6);
/* Theme-aware colors */
--background: #ffffff;
--surface: #F4F4F5;
--surface-hover: #e4e4e7;
--border: #e4e4e7;
--header-text: #000000;
--nav-mobile-bg: var(--marreta);
--nav-mobile-text: #ffffff;
--nav-desktop-text: #333333;
--nav-desktop-hover: #007bff;
--input-bg: #F4F4F5;
--toast-error: rgb(247, 102, 97);
--toast-warning: rgb(247, 152, 97);
--container_spacing: 24px;
@include mixin.devices(desktop) {
--container_spacing: 64px;
}
}
/* Dark theme */
[data-theme="dark"] {
@include mixin.create-color('marreta', #60A5FA);
@include mixin.create-color('text', #e5e5e5);
@include mixin.create-color('textmuted', #a1a1aa);
@include mixin.create-color('link', #60A5FA);
--background: #000;
--surface: #1f1f1f;
--surface-hover: #2a2a2a;
--border: #2a2a2a;
--header-text: #ffffff;
--nav-mobile-bg: var(--marreta);
--nav-mobile-text: #ffffff;
--nav-desktop-text: #e5e5e5;
--nav-desktop-hover: #60A5FA;
--input-bg: #1f1f1f;
--toast-error: rgb(220, 38, 127);
--toast-warning: rgb(245, 158, 11);
}
html {
scroll-behavior: smooth;
}
@media screen and (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}

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

@ -0,0 +1,722 @@
@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: var(--toast-error);
}
&--warning {
background-color: var(--toast-warning);
}
}
header {
display: grid;
grid-template-columns: auto 1fr auto 1fr;
align-items: center;
padding: 0 0 42px 0;
@include mixin.devices(desktop) {
grid-template-columns: 1fr 2fr auto 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: var(--header-text);
}
}
.fast_buttons {
display: flex;
gap: 8px;
}
.theme-controls {
display: flex;
justify-content: center;
align-items: center;
padding: 0 16px;
@include mixin.devices(desktop) {
padding: 0;
}
.theme-toggle {
background: none;
border: 2px solid var(--border);
border-radius: 50%;
width: 40px;
height: 40px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: all 0.3s ease;
&:hover {
border-color: var(--marreta);
background-color: var(--surface-hover);
}
.icon {
width: 18px;
height: 18px;
position: absolute;
transition: all 0.3s ease;
&--sun {
opacity: 1;
transform: rotate(0deg) scale(1);
}
&--moon {
opacity: 0;
transform: rotate(180deg) scale(0.8);
}
[data-theme="dark"] & {
filter: invert(1);
}
}
[data-theme="dark"] & {
.icon {
&--sun {
opacity: 0;
transform: rotate(-180deg) scale(0.8);
}
&--moon {
opacity: 1;
transform: rotate(0deg) scale(1);
}
}
}
}
}
nav {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--nav-mobile-bg);
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: var(--nav-mobile-text);
text-decoration: none;
@include mixin.devices(desktop) {
color: var(--nav-desktop-text);
font-size: initial;
padding: 0;
border-bottom: 0;
}
&:hover {
color: var(--nav-mobile-text);
@include mixin.devices(desktop) {
color: var(--nav-desktop-hover);
}
}
}
.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: var(--nav-desktop-text);
cursor: pointer;
}
&:hover {
color: rgba(255,255,255,0.5);
@include mixin.devices(desktop) {
color: var(--nav-desktop-hover);
}
}
}
&__menu {
@include mixin.devices(desktop) {
position: absolute;
top: 110%;
left: 0;
border-radius: 16px;
background-color: var(--surface);
border: 4px solid var(--surface);
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: var(--background);
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: var(--surface);
border: 4px solid var(--surface);
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: var(--background);
display: flex;
align-items: center;
&:hover {
color: var(--marreta);
}
span {
display: inline-block;
}
}
.name {
width: 140px;
}
}
&.open {
.extension__toggle {
background-color: var(--surface);
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: var(--header-text);
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;
transition: filter 0.3s ease;
[data-theme="dark"] & {
filter: invert(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: var(--input-bg);
padding: 16px 0 16px 44px;
border: 0;
border-radius: 8px;
width: 100%;
box-sizing: border-box;
position: relative;
line-height: 1.3em;
color: var(--text);
}
}
.paste {
background: var(--input-bg);
background: linear-gradient(90deg, transparent 0%, var(--input-bg) 30%, var(--input-bg) 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 {
transition: filter 0.3s ease;
[data-theme="dark"] & {
filter: invert(1);
}
}
}
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: var(--surface);
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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 399 B

211
app/bin/cleanup Normal file
View file

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

196
app/bin/proxy Normal file
View file

@ -0,0 +1,196 @@
#!/usr/bin/env php
<?php
/**
* Proxy List Cache Updater
*
* Downloads proxy list from the URL specified in the PROXY_LIST environment variable
* and stores it in the cache directory for reuse.
* This script should be run daily via cron to keep the proxy list updated.
*
* Supported proxy list formats:
* 1. http://USER:PASSWORD@HOST:PORT
* 2. IP:PORT:USER:PASSWORD
*/
require_once __DIR__ . '/../vendor/autoload.php';
use League\CLImate\CLImate;
use Dotenv\Dotenv;
use Curl\Curl;
$climate = new CLImate();
$climate->bold()->out('Proxy List Cache Updater');
$climate->br();
try {
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
$climate->out('Environment variables loaded');
} catch (\Exception $e) {
$climate->yellow()->out('Warning: ' . $e->getMessage());
exit(0);
}
if (!defined('CACHE_DIR')) {
define('CACHE_DIR', __DIR__ . '/../cache');
}
if (!isset($_ENV['PROXY_LIST']) || empty($_ENV['PROXY_LIST'])) {
$climate->yellow()->out('PROXY_LIST environment variable not set. No proxies to cache.');
exit(0);
}
$proxyListUrl = $_ENV['PROXY_LIST'];
$proxyCachePath = CACHE_DIR . '/proxy_list.json';
// Download proxy list from URL
$climate->out('Downloading proxy list from: ' . $proxyListUrl);
$proxyList = downloadProxyList($proxyListUrl, $climate);
if ($proxyList === false) {
$climate->red()->out('Failed to download proxy list from URL: ' . $proxyListUrl);
exit(1);
}
$climate->green()->out('Proxy list downloaded successfully (' . strlen($proxyList) . ' bytes)');
if (!is_dir(CACHE_DIR)) {
if (!mkdir(CACHE_DIR, 0755, true)) {
$climate->red()->out('Failed to create cache directory: ' . CACHE_DIR);
exit(1);
}
}
$climate->out('Parsing proxy list from environment variable...');
$proxies = parseProxyList($proxyList);
if (empty($proxies)) {
$climate->red()->out('No valid proxies found in PROXY_LIST. Supported formats are:');
$climate->red()->out('1. http://USER:PASSWORD@HOST:PORT');
$climate->red()->out('2. IP:PORT:USER:PASSWORD');
exit(1);
}
$climate->out('Found ' . count($proxies) . ' valid proxies.');
if (file_put_contents($proxyCachePath, json_encode($proxies))) {
$climate->green()->out('Proxy list successfully cached to: ' . $proxyCachePath);
} else {
$climate->red()->out('Failed to write proxy list to cache file: ' . $proxyCachePath);
exit(1);
}
/**
* Parse proxy list from environment variable
*
* @param string $proxyListString Proxy list in format http://USER:PASSWORD@HOST:PORT or IP:PORT:USER:PASSWORD
* @return array Array of valid proxy URLs
*/
function parseProxyList($proxyListString) {
$proxies = [];
$lines = preg_split('/[\r\n,]+/', $proxyListString);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
// Format 1: http://USER:PASSWORD@HOST:PORT
if (preg_match('/^https?:\/\/[^:]+:[^@]+@[^:]+:\d+$/i', $line)) {
$proxies[] = $line;
continue;
}
// Format 2: IP:PORT:USER:PASSWORD
if (preg_match('/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+):([^:]+):(.+)$/', $line, $matches)) {
$ip = $matches[1];
$port = $matches[2];
$user = $matches[3];
$password = $matches[4];
// Convert to standard format
$proxies[] = "http://{$user}:{$password}@{$ip}:{$port}";
}
}
return $proxies;
}
/**
* Download proxy list from URL using php-curl-class
*
* @param string $url URL to download proxy list from
* @param CLImate $climate CLImate instance for output
* @return string|false Downloaded content or false on failure
*/
function downloadProxyList($url, $climate = null) {
$curl = new Curl();
// Configure cURL options
$curl->setTimeout(30);
$curl->setConnectTimeout(10);
$curl->setUserAgent('Marreta Proxy Updater/1.0');
$curl->setHeader('Accept', 'text/plain, text/html, */*');
$curl->setHeader('Accept-Encoding', 'gzip, deflate');
$curl->setOpt(CURLOPT_FOLLOWLOCATION, true);
$curl->setOpt(CURLOPT_MAXREDIRS, 3);
$curl->setOpt(CURLOPT_SSL_VERIFYPEER, false);
$curl->setOpt(CURLOPT_SSL_VERIFYHOST, false);
try {
if ($climate) {
$climate->out('Making HTTP request with php-curl-class...');
}
$curl->get($url);
if ($curl->error) {
$errorMsg = 'cURL request failed: ' . $curl->errorMessage . ' (Code: ' . $curl->errorCode . ')';
if ($climate) {
$climate->red()->out($errorMsg);
} else {
error_log($errorMsg);
}
return false;
}
$statusCode = $curl->httpStatusCode;
if ($climate) {
$climate->out('HTTP Status Code: ' . $statusCode);
}
if ($statusCode === 200) {
$content = $curl->response;
if ($climate) {
$contentType = $curl->responseHeaders['Content-Type'] ?? 'unknown';
$climate->out('Content-Type: ' . $contentType);
$climate->out('Content-Length: ' . strlen($content) . ' bytes');
}
return $content;
}
if ($climate) {
$climate->yellow()->out('Unexpected HTTP status code: ' . $statusCode);
}
return false;
} catch (\Exception $e) {
$errorMsg = 'Unexpected error during download: ' . $e->getMessage();
if ($climate) {
$climate->red()->out($errorMsg);
} else {
error_log($errorMsg);
}
return false;
} finally {
$curl->close();
}
}

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-curl-class/php-curl-class": "^11.0",
"php-webdriver/webdriver": "^1.15", "php-webdriver/webdriver": "^1.15",
"monolog/monolog": "^3.8.1", "monolog/monolog": "^3.8.1",
"nikic/fast-route": "^1.3" "nikic/fast-route": "^1.3",
"league/climate": "^3.8"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View file

@ -1,84 +1,47 @@
<?php <?php
/** /**
* Main configuration file * System configuration manager
* Arquivo de configuração principal * - Loads and validates environment variables
* * - Defines global constants for system settings
* This file contains all global system settings, including: * - Manages security rules and external service configs
* 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
*/ */
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
try { try {
// Initialize environment variables // Load environment variables
// Inicializa as variáveis de ambiente
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load(); $dotenv->load();
// Validate required fields // Validate required fields
// Valida campos obrigatórios
$dotenv->required([ $dotenv->required([
'SITE_NAME', 'SITE_NAME',
'SITE_DESCRIPTION', 'SITE_DESCRIPTION',
'SITE_URL' 'SITE_URL'
])->notEmpty(); ])->notEmpty();
// Custom URL validation // Core system settings
// Validação personalizada de URL
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
*/
define('SITE_NAME', $_ENV['SITE_NAME']); define('SITE_NAME', $_ENV['SITE_NAME']);
define('SITE_DESCRIPTION', $_ENV['SITE_DESCRIPTION']); define('SITE_DESCRIPTION', $_ENV['SITE_DESCRIPTION']);
define('SITE_URL', $_ENV['SITE_URL']); define('SITE_URL', $_ENV['SITE_URL']);
define('CLEANUP_DAYS', $_ENV['CLEANUP_DAYS'] ?? 0);
// Optional settings with default values // Optional settings with defaults
// Configurações opcionais com valores padrão
define('DNS_SERVERS', $_ENV['DNS_SERVERS'] ?? '1.1.1.1, 8.8.8.8'); define('DNS_SERVERS', $_ENV['DNS_SERVERS'] ?? '1.1.1.1, 8.8.8.8');
define('DISABLE_CACHE', isset($_ENV['DISABLE_CACHE']) ? define('DISABLE_CACHE', isset($_ENV['DISABLE_CACHE']) ? filter_var($_ENV['DISABLE_CACHE'], FILTER_VALIDATE_BOOLEAN) : false);
filter_var($_ENV['DISABLE_CACHE'], FILTER_VALIDATE_BOOLEAN) : false);
define('SELENIUM_HOST', $_ENV['SELENIUM_HOST'] ?? 'localhost:4444'); define('SELENIUM_HOST', $_ENV['SELENIUM_HOST'] ?? 'localhost:4444');
define('CACHE_DIR', __DIR__ . '/cache'); define('CACHE_DIR', __DIR__ . '/cache');
define('LANGUAGE', $_ENV['LANGUAGE'] ?? 'pt-br'); define('LANGUAGE', $_ENV['LANGUAGE'] ?? 'pt-br');
/** // Logging configuration
* Redis settings define('LOG_LEVEL', $_ENV['LOG_LEVEL'] ?? 'WARNING'); // DEBUG, INFO, WARNING, ERROR, CRITICAL
* 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
define('LOG_DAYS_TO_KEEP', 7); define('LOG_DAYS_TO_KEEP', 7);
/** // S3 cache configuration
* S3 Cache settings define('S3_CACHE_ENABLED', isset($_ENV['S3_CACHE_ENABLED']) ? filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN) : false);
* Configurações de Cache S3
*/
define('S3_CACHE_ENABLED', isset($_ENV['S3_CACHE_ENABLED']) ?
filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN) : false);
if (S3_CACHE_ENABLED) { 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([ $dotenv->required([
'S3_ACCESS_KEY', 'S3_ACCESS_KEY',
'S3_SECRET_KEY', 'S3_SECRET_KEY',
@ -94,14 +57,21 @@ try {
define('S3_ENDPOINT', $_ENV['S3_ENDPOINT'] ?? null); define('S3_ENDPOINT', $_ENV['S3_ENDPOINT'] ?? null);
} }
/** // Load security rules
* Load system configurations
* Carrega as configurações do sistema
*/
define('BLOCKED_DOMAINS', require __DIR__ . '/data/blocked_domains.php'); define('BLOCKED_DOMAINS', require __DIR__ . '/data/blocked_domains.php');
define('DOMAIN_RULES', require __DIR__ . '/data/domain_rules.php'); define('DOMAIN_RULES', require __DIR__ . '/data/domain_rules.php');
define('GLOBAL_RULES', require __DIR__ . '/data/global_rules.php'); define('GLOBAL_RULES', require __DIR__ . '/data/global_rules.php');
// Load DMCA domains from JSON file
$dmcaDomainsFile = __DIR__ . '/cache/dmca_domains.json';
if (file_exists($dmcaDomainsFile)) {
$dmcaDomainsJson = file_get_contents($dmcaDomainsFile);
$dmcaDomains = json_decode($dmcaDomainsJson, true);
define('DMCA_DOMAINS', is_array($dmcaDomains) ? $dmcaDomains : []);
} else {
define('DMCA_DOMAINS', []);
}
} catch (Dotenv\Exception\ValidationException $e) { } catch (Dotenv\Exception\ValidationException $e) {
die('Environment Error: ' . $e->getMessage()); die('Environment Error: ' . $e->getMessage());
} catch (Exception $e) { } catch (Exception $e) {

View file

@ -2,17 +2,18 @@
/** /**
* List of blocked domains * List of blocked domains
* Lista de domínios bloqueados
* *
* Defines domains that cannot be accessed by the system * Defines domains that cannot be accessed by the system
* due to usage policies or technical restrictions * 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
*/ */
$host = parse_url(defined('SITE_URL') ? SITE_URL : '', PHP_URL_HOST);
return [ return [
// News sites / Sites de notícias $host,
//-- Content behind login access / Conteúdo fica atrás de um acesso de login 'localhost',
'127.0.0.1',
// News sites
//-- Content behind login access/hard paywall
'wsj.com', 'wsj.com',
'piaui.folha.uol.com.br', 'piaui.folha.uol.com.br',
'economist.com', 'economist.com',
@ -31,14 +32,16 @@ return [
'mittelbayerische.de', 'mittelbayerische.de',
'josimarfootball.com', 'josimarfootball.com',
'nordsee-zeitung.de', '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', 'bloomberg.com',
'sportskeeda.com', 'sportskeeda.com',
'kansascity.com', 'kansascity.com',
'fastcompany.com', 'fastcompany.com',
'expressnews.com', 'expressnews.com',
'nydailynews.com', 'nydailynews.com',
// Tracking services / Serviços de rastreamento //-- Tracking services
'metaffiliation.com', 'metaffiliation.com',
'google-analytics.com', 'google-analytics.com',
'googletagmanager.com', 'googletagmanager.com',
@ -59,7 +62,7 @@ return [
'fullstory.com', 'fullstory.com',
'heap.io', 'heap.io',
'clearbrain.com', 'clearbrain.com',
// Social networks / Redes sociais //-- Social networks
'facebook.com', 'facebook.com',
'instagram.com', 'instagram.com',
'twitter.com', 'twitter.com',
@ -73,7 +76,7 @@ return [
'redd.it', 'redd.it',
'bsky.app', 'bsky.app',
'threads.net', 'threads.net',
// Streaming services / Serviços de streaming //-- Streaming services
'netflix.com', 'netflix.com',
'hulu.com', 'hulu.com',
'disneyplus.com', 'disneyplus.com',
@ -81,32 +84,32 @@ return [
'spotify.com', 'spotify.com',
'youtube.com', 'youtube.com',
'twitch.tv', 'twitch.tv',
// E-commerce sites / Sites de comércio eletrônico //-- E-commerce sites
'amazon.com', 'amazon.com',
'ebay.com', 'ebay.com',
'aliexpress.com', 'aliexpress.com',
'mercadolivre.com.br', 'mercadolivre.com.br',
'shopify.com', 'shopify.com',
// File sharing / Compartilhamento de arquivos //-- File sharing
'mega.nz', 'mega.nz',
'mediafire.com', 'mediafire.com',
'wetransfer.com', 'wetransfer.com',
'dropbox.com', 'dropbox.com',
'torrent9.pe', 'torrent9.pe',
'thepiratebay.org', 'thepiratebay.org',
// Adult sites / Sites adultos //-- Adult sites
'pornhub.com', 'pornhub.com',
'xvideos.com', 'xvideos.com',
'xnxx.com', 'xnxx.com',
'onlyfans.com', 'onlyfans.com',
'privacy.com.br', 'privacy.com.br',
'fatalmodel.com', 'fatalmodel.com',
// Betting and gaming / Apostas e jogos //-- Betting and gaming
'bet365.com', 'bet365.com',
'betfair.com', 'betfair.com',
'pokerstars.com', 'pokerstars.com',
'casino.com', 'casino.com',
// Other popular sites / Outros sites populares //-- Other popular sites
'github.com', 'github.com',
'stackoverflow.com', 'stackoverflow.com',
'wikipedia.org', 'wikipedia.org',
@ -123,7 +126,6 @@ return [
'jusbrasil.com.br', 'jusbrasil.com.br',
'glassdoor.com.br', 'glassdoor.com.br',
'gov.br', 'gov.br',
'medium.com',
'stackoverflow.com', 'stackoverflow.com',
'hoteis.com', 'hoteis.com',
'amazon.com', 'amazon.com',

View file

@ -2,50 +2,60 @@
/** /**
* Specific rule configurations for individual domains * 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: * 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 * - userAgent: Define custom User-Agent for the domain
* - headers: Custom HTTP headers for requests / Headers HTTP personalizados para requisições * - headers: Custom HTTP headers for requests
* - idElementRemove: Array of HTML IDs to be removed / Array de IDs HTML que devem ser removidos da página * - idElementRemove: Array of HTML IDs to be removed
* - classElementRemove: Array of HTML classes to be removed / Array de classes HTML que devem ser removidas * - classElementRemove: Array of HTML classes to be removed
* - scriptTagRemove: Array of scripts to be removed (partial match) / Array de scripts que devem ser removidos (partial match) * - scriptTagRemove: Array of scripts to be removed (partial match)
* - cookies: Associative array of cookies to be set (null removes cookie) / Array associativo de cookies a serem definidos (null remove o cookie) * - cookies: Associative array of cookies to be set (null removes cookie)
* - classAttrRemove: Array of classes to be removed from elements / Array de classes a serem removidas de elementos * - classAttrRemove: Array of classes to be removed from elements
* - customCode: String containing custom JavaScript code / String contendo código JavaScript personalizado * - customCode: String containing custom JavaScript code
* - customStyle: String containing custom CSS code / String contendo código CSS personalizado * - customStyle: String containing custom CSS code
* - excludeGlobalRules: Associative array of global rules to exclude for this domain / Array associativo de regras globais a serem excluídas para este domínio * - proxy: Enable proxy in Guzzle or Selenium requests
* Example / Exemplo: * - excludeGlobalRules: Associative array of global rules to exclude for this domain
* Example:
* 'excludeGlobalRules' => [ * 'excludeGlobalRules' => [
* 'scriptTagRemove' => ['gtm.js', 'ga.js'], // Excludes specific scripts from global rules / Exclui scripts específicos das regras globais * 'scriptTagRemove' => ['gtm.js', 'ga.js'],
* 'classElementRemove' => ['subscription'] // Excludes specific classes from global rules / Exclui classes específicas das regras globais * 'classElementRemove' => ['subscription']
* ] * ]
* - fetchStrategies: String indicating which fetch strategy to use. Available values: / String indicando qual estratégia de fetch usar. Valores disponíveis: * - fetchStrategies: String indicating which fetch strategy to use. Available values:
* - fetchContent: Use standard fetch with domain rules / Usa fetch padrão com regras do domínio * - fetchContent: Use standard fetch with domain rules
* - fetchFromWaybackMachine: Try to fetch from Internet Archive / Tenta buscar do Internet Archive * - fetchFromWaybackMachine: Try to fetch from Internet Archive
* - fetchFromSelenium: Use Selenium for extraction / Usa Selenium para extração * - fetchFromSelenium: Use Selenium for extraction
* - socialReferrers: Add random social media headers / Adiciona headers randomicos de redes sociais * - socialReferrers: Add random social media headers
* - fromGoogleBot: Adds simulation of request coming from Google Bot / Adiciona simulação de requisição vinda do Google Bot * - fromGoogleBot: Adds simulation of request coming from Google Bot
* - removeElementsByTag: Remove specific elements via DOM / Remove elementos especificos via DOM * - removeElementsByTag: Remove specific elements via DOM
* - removeCustomAttr: Remove custom attributes from elements / Remove custom attributes from elements * - removeCustomAttr: Remove custom attributes from elements
* - urlMods: Modify the URL before fetching content.
* Example:
* 'urlMods' => [
* 'query' => [
* [
* 'key' => 'amp',
* 'value' => '1'
* ]
* ]
* ]
*/ */
return [ return [
'nsctotal.com.br' => [ 'nsctotal.com.br' => [
'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' 'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
], ],
'elcorreo.com' => [ 'elcorreo.com' => [
'idElementRemove' => ['didomi-popup','engagement-top'], 'idElementRemove' => ['didomi-popup', 'engagement-top'],
'classElementRemove' => ['content-exclusive-bg'], 'classElementRemove' => ['content-exclusive-bg'],
'classAttrRemove' => ['didomi-popup-open','paywall'], 'classAttrRemove' => ['didomi-popup-open', 'paywall'],
'fromGoogleBot' => true, 'fromGoogleBot' => true,
'removeElementsByTag' => ['style'], 'removeElementsByTag' => ['style'],
'removeCustomAttr' => ['hidden','data-*'] 'removeCustomAttr' => ['hidden', 'data-*']
],
'wired.com' => [
'scriptTagRemove' => ['.js'],
],
'newyorker.com' => [
'scriptTagRemove' => ['.js'],
], ],
'globo.com' => [ 'globo.com' => [
'idElementRemove' => ['cookie-banner-lgpd', 'paywall-cpt', 'mc-read-more-wrapper', 'paywall-cookie-content', 'paywall-cpt'], 'idElementRemove' => ['cookie-banner-lgpd', 'paywall-cpt', 'mc-read-more-wrapper', 'paywall-cookie-content', 'paywall-cpt'],
@ -61,18 +71,33 @@ return [
'gauchazh.clicrbs.com.br' => [ 'gauchazh.clicrbs.com.br' => [
'idElementRemove' => ['paywallTemplate'], 'idElementRemove' => ['paywallTemplate'],
'classAttrRemove' => ['m-paid-content', 'paid-content-apply'], 'classAttrRemove' => ['m-paid-content', 'paid-content-apply'],
'scriptTagRemove' => ['vendors-8'], 'scriptTagRemove' => ['vendors-9','vendors-10','vendors-11'],
'excludeGlobalRules' => [ 'excludeGlobalRules' => [
'classElementRemove' => ['paid-content'] 'classElementRemove' => ['paid-content']
], ],
'fetchStrategies' => 'fetchFromSelenium', 'proxy' => true,
], ],
'reuters.com' => [ 'reuters.com' => [
'classElementRemove' => ['leaderboard__container'], 'classElementRemove' => ['leaderboard__container'],
'fetchStrategies' => 'fetchFromSelenium', 'fetchStrategies' => 'fetchFromSelenium',
], ],
'cnn.com' => [
'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' => [ 'fortune.com' => [
'classElementRemove' => ['latest-popular-module','own','drawer-menu'], 'classElementRemove' => ['latest-popular-module', 'own', 'drawer-menu'],
'fetchStrategies' => 'fetchFromSelenium', 'fetchStrategies' => 'fetchFromSelenium',
'browser' => 'chrome', 'browser' => 'chrome',
'scriptTagRemove' => ['queryly.com'], 'scriptTagRemove' => ['queryly.com'],
@ -81,10 +106,6 @@ return [
'idElementRemove' => ['cboxOverlay'], 'idElementRemove' => ['cboxOverlay'],
'fetchStrategies' => 'fetchFromSelenium', 'fetchStrategies' => 'fetchFromSelenium',
], ],
'washingtonpost.com' => [
'classElementRemove' => ['paywall-overlay'],
'fetchStrategies' => 'fetchFromSelenium',
],
'oantagonista.com.br' => [ 'oantagonista.com.br' => [
'fetchStrategies' => 'fetchFromSelenium', 'fetchStrategies' => 'fetchFromSelenium',
], ],
@ -137,6 +158,21 @@ return [
'paywall_access' => 'true' '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' => [ 'uol.com.br' => [
'scriptTagRemove' => ['me.jsuol.com.br', 'c.jsuol.com.br'], 'scriptTagRemove' => ['me.jsuol.com.br', 'c.jsuol.com.br'],
'classElementRemove' => ['header-top-wrapper'], 'classElementRemove' => ['header-top-wrapper'],
@ -150,6 +186,12 @@ return [
'nzherald.co.nz' => [ 'nzherald.co.nz' => [
'fetchStrategies' => 'fetchFromSelenium', 'fetchStrategies' => 'fetchFromSelenium',
], ],
'onetz.de' => [
'idElementRemove' => ['checkout-container'],
'classElementRemove' => ['tp-backdrop','dm-nobg'],
'classAttrRemove' => ['field-dnt-body-pp'],
'scriptTagRemove' => ['.js'],
],
'opovo.com.br' => [ 'opovo.com.br' => [
'classElementRemove' => ['screen-loading', 'overlay-advise'] 'classElementRemove' => ['screen-loading', 'overlay-advise']
], ],
@ -159,7 +201,8 @@ return [
] ]
], ],
'theverge.com' => [ 'theverge.com' => [
'fetchStrategies' => 'fetchFromSelenium', 'scriptTagRemove' => 'zephr',
'classElementRemove' => 'zephr'
], ],
'economist.com' => [ 'economist.com' => [
'cookies' => [ 'cookies' => [
@ -178,15 +221,8 @@ return [
} }
' '
], ],
'ft.com' => [
'cookies' => [
'next-flags' => null,
'next:ads' => null
],
'fromGoogleBot' => true
],
'nytimes.com' => [ 'nytimes.com' => [
'idElementRemove' => ['gateway-content','site-index'], 'idElementRemove' => ['gateway-content', 'site-index', 'complianceOverlay'],
'customCode' => ' 'customCode' => '
setTimeout(function() { setTimeout(function() {
const walk = document.createTreeWalker( const walk = document.createTreeWalker(
@ -235,7 +271,7 @@ return [
position: relative !important; position: relative !important;
} }
', ',
'fetchStrategies' => 'fetchFromSelenium', 'fetchStrategies' => 'fetchFromWaybackMachine',
'excludeGlobalRules' => [ 'excludeGlobalRules' => [
'scriptTagRemove' => [ 'scriptTagRemove' => [
'gtm.js', 'gtm.js',
@ -336,8 +372,358 @@ return [
'_pctx' => null '_pctx' => null
] ]
], ],
'thestar.com' => [
// Domain test 'classElementRemove' => ['subscriber-offers', 'subscriber-only', 'subscription-required', 'redacted-overlay', 'subscriber-hide', 'tnt-ads-container'],
'customCode' => '
window.localStorage.clear();
document.addEventListener("DOMContentLoaded", () => {
const paywall = document.querySelectorAll(\'div.subscriber-offers\');
paywall.forEach(el => { el.remove(); });
const subscriber_only = document.querySelectorAll(\'div.subscriber-only\');
for (const elem of subscriber_only) {
if (elem.classList.contains(\'encrypted-content\') && typeof DOMPurify !== \'undefined\' && typeof unscramble !== \'undefined\') {
const parser = new DOMParser();
const doc = parser.parseFromString(\'<div>\' + DOMPurify.sanitize(unscramble(elem.innerText)) + \'</div>\', \'text/html\');
const content_new = doc.querySelector(\'div\');
elem.parentNode.replaceChild(content_new, elem);
}
elem.removeAttribute(\'style\');
elem.removeAttribute(\'class\');
}
const banners = document.querySelectorAll(\'div.subscription-required, div.redacted-overlay, div.subscriber-hide, div.tnt-ads-container\');
banners.forEach(el => { el.remove(); });
const ads = document.querySelectorAll(\'div.tnt-ads-container, div[class*="adLabelWrapper"]\');
ads.forEach(el => { el.remove(); });
const recommendations = document.querySelectorAll(\'div[id^="tncms-region-article"]\');
recommendations.forEach(el => { el.remove(); });
});
'
],
'niagarafallsreview.ca' => [
'classElementRemove' => ['subscriber-offers', 'subscriber-only', 'subscription-required', 'redacted-overlay', 'subscriber-hide', 'tnt-ads-container'],
'customCode' => '
window.localStorage.clear();
document.addEventListener("DOMContentLoaded", () => {
const paywall = document.querySelectorAll(\'div.subscriber-offers\');
paywall.forEach(el => { el.remove(); });
const subscriber_only = document.querySelectorAll(\'div.subscriber-only\');
for (const elem of subscriber_only) {
if (elem.classList.contains(\'encrypted-content\') && typeof DOMPurify !== \'undefined\' && typeof unscramble !== \'undefined\') {
const parser = new DOMParser();
const doc = parser.parseFromString(\'<div>\' + DOMPurify.sanitize(unscramble(elem.innerText)) + \'</div>\', \'text/html\');
const content_new = doc.querySelector(\'div\');
elem.parentNode.replaceChild(content_new, elem);
}
elem.removeAttribute(\'style\');
elem.removeAttribute(\'class\');
}
const banners = document.querySelectorAll(\'div.subscription-required, div.redacted-overlay, div.subscriber-hide, div.tnt-ads-container\');
banners.forEach(el => { el.remove(); });
const ads = document.querySelectorAll(\'div.tnt-ads-container, div[class*="adLabelWrapper"]\');
ads.forEach(el => { el.remove(); });
const recommendations = document.querySelectorAll(\'div[id^="tncms-region-article"]\');
recommendations.forEach(el => { el.remove(); });
});
'
],
'thepeterboroughexaminer.com' => [
'classElementRemove' => ['subscriber-offers', 'subscriber-only', 'subscription-required', 'redacted-overlay', 'subscriber-hide', 'tnt-ads-container'],
'customCode' => '
window.localStorage.clear();
document.addEventListener("DOMContentLoaded", () => {
const paywall = document.querySelectorAll(\'div.subscriber-offers\');
paywall.forEach(el => { el.remove(); });
const subscriber_only = document.querySelectorAll(\'div.subscriber-only\');
for (const elem of subscriber_only) {
if (elem.classList.contains(\'encrypted-content\') && typeof DOMPurify !== \'undefined\' && typeof unscramble !== \'undefined\') {
const parser = new DOMParser();
const doc = parser.parseFromString(\'<div>\' + DOMPurify.sanitize(unscramble(elem.innerText)) + \'</div>\', \'text/html\');
const content_new = doc.querySelector(\'div\');
elem.parentNode.replaceChild(content_new, elem);
}
elem.removeAttribute(\'style\');
elem.removeAttribute(\'class\');
}
const banners = document.querySelectorAll(\'div.subscription-required, div.redacted-overlay, div.subscriber-hide, div.tnt-ads-container\');
banners.forEach(el => { el.remove(); });
const ads = document.querySelectorAll(\'div.tnt-ads-container, div[class*="adLabelWrapper"]\');
ads.forEach(el => { el.remove(); });
const recommendations = document.querySelectorAll(\'div[id^="tncms-region-article"]\');
recommendations.forEach(el => { el.remove(); });
});
'
],
'therecord.com' => [
'classElementRemove' => ['subscriber-offers', 'subscriber-only', 'subscription-required', 'redacted-overlay', 'subscriber-hide', 'tnt-ads-container'],
'customCode' => '
window.localStorage.clear();
document.addEventListener("DOMContentLoaded", () => {
const paywall = document.querySelectorAll(\'div.subscriber-offers\');
paywall.forEach(el => { el.remove(); });
const subscriber_only = document.querySelectorAll(\'div.subscriber-only\');
for (const elem of subscriber_only) {
if (elem.classList.contains(\'encrypted-content\') && typeof DOMPurify !== \'undefined\' && typeof unscramble !== \'undefined\') {
const parser = new DOMParser();
const doc = parser.parseFromString(\'<div>\' + DOMPurify.sanitize(unscramble(elem.innerText)) + \'</div>\', \'text/html\');
const content_new = doc.querySelector(\'div\');
elem.parentNode.replaceChild(content_new, elem);
}
elem.removeAttribute(\'style\');
elem.removeAttribute(\'class\');
}
const banners = document.querySelectorAll(\'div.subscription-required, div.redacted-overlay, div.subscriber-hide, div.tnt-ads-container\');
banners.forEach(el => { el.remove(); });
const ads = document.querySelectorAll(\'div.tnt-ads-container, div[class*="adLabelWrapper"]\');
ads.forEach(el => { el.remove(); });
const recommendations = document.querySelectorAll(\'div[id^="tncms-region-article"]\');
recommendations.forEach(el => { el.remove(); });
});
'
],
'thespec.com' => [
'classElementRemove' => ['subscriber-offers', 'subscriber-only', 'subscription-required', 'redacted-overlay', 'subscriber-hide', 'tnt-ads-container'],
'customCode' => '
window.localStorage.clear();
document.addEventListener("DOMContentLoaded", () => {
const paywall = document.querySelectorAll(\'div.subscriber-offers\');
paywall.forEach(el => { el.remove(); });
const subscriber_only = document.querySelectorAll(\'div.subscriber-only\');
for (const elem of subscriber_only) {
if (elem.classList.contains(\'encrypted-content\') && typeof DOMPurify !== \'undefined\' && typeof unscramble !== \'undefined\') {
const parser = new DOMParser();
const doc = parser.parseFromString(\'<div>\' + DOMPurify.sanitize(unscramble(elem.innerText)) + \'</div>\', \'text/html\');
const content_new = doc.querySelector(\'div\');
elem.parentNode.replaceChild(content_new, elem);
}
elem.removeAttribute(\'style\');
elem.removeAttribute(\'class\');
}
const banners = document.querySelectorAll(\'div.subscription-required, div.redacted-overlay, div.subscriber-hide, div.tnt-ads-container\');
banners.forEach(el => { el.remove(); });
const ads = document.querySelectorAll(\'div.tnt-ads-container, div[class*="adLabelWrapper"]\');
ads.forEach(el => { el.remove(); });
const recommendations = document.querySelectorAll(\'div[id^="tncms-region-article"]\');
recommendations.forEach(el => { el.remove(); });
});
'
],
'wellandtribune.ca' => [
'classElementRemove' => ['subscriber-offers', 'subscriber-only', 'subscription-required', 'redacted-overlay', 'subscriber-hide', 'tnt-ads-container'],
'customCode' => '
window.localStorage.clear();
document.addEventListener("DOMContentLoaded", () => {
const paywall = document.querySelectorAll(\'div.subscriber-offers\');
paywall.forEach(el => { el.remove(); });
const subscriber_only = document.querySelectorAll(\'div.subscriber-only\');
for (const elem of subscriber_only) {
if (elem.classList.contains(\'encrypted-content\') && typeof DOMPurify !== \'undefined\' && typeof unscramble !== \'undefined\') {
const parser = new DOMParser();
const doc = parser.parseFromString(\'<div>\' + DOMPurify.sanitize(unscramble(elem.innerText)) + \'</div>\', \'text/html\');
const content_new = doc.querySelector(\'div\');
elem.parentNode.replaceChild(content_new, elem);
}
elem.removeAttribute(\'style\');
elem.removeAttribute(\'class\');
}
const banners = document.querySelectorAll(\'div.subscription-required, div.redacted-overlay, div.subscriber-hide, div.tnt-ads-container\');
banners.forEach(el => { el.remove(); });
const ads = document.querySelectorAll(\'div.tnt-ads-container, div[class*="adLabelWrapper"]\');
ads.forEach(el => { el.remove(); });
const recommendations = document.querySelectorAll(\'div[id^="tncms-region-article"]\');
recommendations.forEach(el => { el.remove(); });
});
'
],
'time.com' => [
'headers' => [
'User-Agent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
'Cookie' => 'nyt-a=; nyt-gdpr=0; nyt-geo=DE; nyt-privacy=1',
'Referer' => 'https://www.google.com/'
],
'customCode' => '
window.localStorage.clear();
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'div[data-testid="inline-message"], div[id^="ad-"], div[id^="leaderboard-"], div.expanded-dock, div.pz-ad-box, div[id="top-wrapper"], div[id="bottom-wrapper"]\');
banners.forEach(el => { el.remove(); });
});
'
],
'architecturaldigest.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'.paywall-bar, div[class^="MessageBannerWrapper-"\');
banners.forEach(el => { el.remove(); });
});
'
],
'bonappetit.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'.paywall-bar, div[class^="MessageBannerWrapper-"\');
banners.forEach(el => { el.remove(); });
});
'
],
'cntraveler.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'.paywall-bar, div[class^="MessageBannerWrapper-"\');
banners.forEach(el => { el.remove(); });
});
'
],
'epicurious.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'.paywall-bar, div[class^="MessageBannerWrapper-"\');
banners.forEach(el => { el.remove(); });
});
'
],
'gq.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'.paywall-bar, div[class^="MessageBannerWrapper-"\');
banners.forEach(el => { el.remove(); });
});
'
],
'vanityfair.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'.paywall-bar, div[class^="MessageBannerWrapper-"\');
banners.forEach(el => { el.remove(); });
});
'
],
'vogue.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'.paywall-bar, div[class^="MessageBannerWrapper-"\');
banners.forEach(el => { el.remove(); });
});
'
],
'americanbanker.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const inlineGate = document.querySelector(\'.inline-gate\');
if (inlineGate) {
inlineGate.classList.remove(\'inline-gate\');
const inlineGated = document.querySelectorAll(\'.inline-gated\');
for (const elem of inlineGated) { elem.classList.remove(\'inline-gated\'); }
}
});
'
],
'washingtonpost.com' => [
'classElementRemove' => ['paywall-overlay'],
'fetchStrategies' => 'fetchFromSelenium',
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
let paywall = document.querySelectorAll(\'div[data-qa$="-ad"], div[id="leaderboard-wrapper"], div[data-qa="subscribe-promo"]\');
paywall.forEach(el => { el.remove(); });
const images = document.querySelectorAll(\'img\');
images.forEach(image => { image.parentElement.style.filter = \'\'; });
const headimage = document.querySelectorAll(\'div .aspect-custom\');
headimage.forEach(image => { image.style.filter = \'\'; });
});
',
'idElementRemove' => ['wall-bottom-drawer-container']
],
'usatoday.com' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const banners = document.querySelectorAll(\'div.roadblock-container, .gnt_nb, [aria-label="advertisement"], div[id="main-frame-error"]\');
banners.forEach(el => { el.remove(); });
});
'
],
'stcatharinesstandard.ca' => [
'proxy' => true,
'idElementRemove' => 'access-offers-modal',
'classElementRemove' => 'modal-backdrop',
'classAttrRemove' => ' modal-open'
],
'medium.com' => [
'headers' => [
'Referer' => 'https://t.co/x?amp=1',
'X-Forwarded-For' => 'none',
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
'Content-Security-Policy' => 'script-src \'self\';'
]
],
'tagesspiegel.de' => [
'headers' => [
'Content-Security-Policy' => 'script-src \'self\';',
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
],
'urlMods' => [
'query' => [
[
'key' => 'amp',
'value' => '1'
]
]
]
],
'nzz.ch' => [
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const paywall = document.querySelector(\'.dynamic-regwall\');
if (paywall) {
paywall.remove();
}
});
'
],
'demorgen.be' => [
'headers' => [
'Cookie' => 'isBot=true; authId=1',
'User-Agent' => 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; Googlebot-News; +http://www.google.com/bot.html) Chrome/121.0.6140.0 Safari/537.36',
'X-Forwarded-For' => 'none',
'Referer' => 'https://news.google.com'
],
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
// remove paywall items
let paywall = document.querySelectorAll(\'script[src*="advertising-cdn.dpgmedia.cloud"], div[data-temptation-position="ARTICLE_BOTTOM"]\');
paywall.forEach(el => { el.remove(); });
// remove empty advert
const advert = document.querySelector(\'div[data-advert-placeholder-collapses]\');
if (advert) {
advert.remove();
}
});
'
],
'ft.com' => [
'cookies' => [
'next-flags' => null,
'next:ads' => null
],
'fromGoogleBot' => true,
'headers' => [
'Referer' => 'https://t.co/x?amp=1'
],
'customCode' => '
document.addEventListener("DOMContentLoaded", () => {
const styleTags = document.querySelectorAll(\'link[rel="stylesheet"]\');
styleTags.forEach(el => {
const href = el.getAttribute(\'href\');
if (href && href.substring(0, 1) === \'/\') {
const updatedHref = href.substring(1).replace(/(https?:\\/\\/.+?)\\/{2,}/, \'$1/\');
el.setAttribute(\'href\', updatedHref);
}
});
setTimeout(() => {
const cookie = document.querySelectorAll(\'.o-cookie-message, .js-article-ribbon, .o-ads, .o-banner, .o-message, .article__content-sign-up\');
cookie.forEach(el => { el.remove(); });
}, 1000);
})
'
],
// Test domain
'altendorfme.github.io' => [ 'altendorfme.github.io' => [
'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'headers' => [ 'headers' => [
@ -345,6 +731,7 @@ return [
'Cache-Control' => 'no-cache', 'Cache-Control' => 'no-cache',
'Pragma' => 'no-cache' 'Pragma' => 'no-cache'
], ],
'proxy' => true,
'idElementRemove' => ['test-id-1', 'paywall'], 'idElementRemove' => ['test-id-1', 'paywall'],
'classElementRemove' => ['test-class-1'], 'classElementRemove' => ['test-class-1'],
'scriptTagRemove' => ['analytics.js', 'test-script.js', 'paywall.js'], 'scriptTagRemove' => ['analytics.js', 'test-script.js', 'paywall.js'],
@ -353,7 +740,7 @@ return [
'consent' => 'accepted', 'consent' => 'accepted',
'session_id' => null 'session_id' => null
], ],
'classAttrRemove' => ['test-attr-1','paywall'], 'classAttrRemove' => ['test-attr-1', 'paywall'],
'customCode' => ' 'customCode' => '
console.log("worked"); console.log("worked");
', ',

View file

@ -2,23 +2,13 @@
/** /**
* Global rule configurations applied to all domains * 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 * Note: These rules can be overridden or disabled for specific domains
* using the 'excludeGlobalRules' configuration in domain_rules.php * 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 [ return [
// HTML classes to be removed from all pages 'proxy' => false,
// Classes HTML a serem removidas de todas as páginas // Classes to be removed from all pages:
'classElementRemove' => [ 'classElementRemove' => [
'subscription', 'subscription',
'subscriber-content', 'subscriber-content',
@ -40,8 +30,7 @@ return [
'signup-overlay', 'signup-overlay',
'onesignal-slidedown-container' 'onesignal-slidedown-container'
], ],
// Scripts to be removed from all pages // Scripts to be removed from all pages:
// Scripts a serem removidos de todas as páginas
'scriptTagRemove' => [ 'scriptTagRemove' => [
'gtm.js', 'gtm.js',
'ga.js', 'ga.js',
@ -80,6 +69,8 @@ return [
'getblue.io', 'getblue.io',
'smartocto.com', 'smartocto.com',
'cdn.pn.vg', '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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 805 B

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

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-fill" viewBox="0 0 16 16">
<path d="M6 .278a.77.77 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278"/>
</svg>

After

Width:  |  Height:  |  Size: 394 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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 796 B

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

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-brightness-high-fill" viewBox="0 0 16 16">
<path d="M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708"/>
</svg>

After

Width:  |  Height:  |  Size: 791 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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 380 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 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 a=e=>{e!==t&&t.classList.remove("open"),e!==o&&o.classList.remove("open")};e.addEventListener("click",e=>{e.stopPropagation(),a(t),t.classList.toggle("open")}),n.addEventListener("click",e=>{e.stopPropagation(),a(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 r=document.getElementById("url");e&&r&&e.addEventListener("click",async e=>{e.preventDefault();try{var t=await navigator.clipboard.readText();r.value=t.trim()}catch(e){console.error("Failed to read clipboard contents",e)}});n=document.getElementById("themeToggle");let c=document.documentElement;e=localStorage.getItem("theme")||"light";c.setAttribute("data-theme",e),n&&n.addEventListener("click",()=>{var e="dark"===c.getAttribute("data-theme")?"light":"dark";c.setAttribute("data-theme",e),localStorage.setItem("theme",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 <?php
namespace Inc;
use Inc\Cache\CacheStorageInterface; use Inc\Cache\CacheStorageInterface;
use Inc\Cache\DiskStorage; use Inc\Cache\DiskStorage;
use Inc\Cache\S3Storage; use Inc\Cache\S3Storage;
use Inc\Cache\RedisStorage; use Inc\Cache\SQLiteStorage;
/** /**
* Class responsible for system cache management * System cache management with multiple storage backends (disk/S3)
* Classe responsável pelo gerenciamento de cache do sistema * Uses SHA-256 hashed URLs as unique identifiers
* * Implements gzip compression for space efficiency
* 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.
*/ */
class Cache class Cache
{ {
/** /** @var CacheStorageInterface Cache storage implementation */
* @var CacheStorageInterface Storage implementation for cache
* @var CacheStorageInterface Implementação de storage para o cache
*/
private $storage; private $storage;
/** /** @var SQLiteStorage SQLite instance for file counting */
* @var RedisStorage Redis instance for file counting private $sqliteStorage;
* @var RedisStorage Instância do Redis para contagem de arquivos
*/
private $redisStorage;
/** /**
* Class constructor * Initializes storage based on configuration
* Construtor da classe * Uses S3Storage if configured and enabled
* * Defaults to SQLiteStorage otherwise (which delegates to DiskStorage)
* Initializes appropriate storage based on configuration
* Inicializa o storage apropriado baseado na configuração
*/ */
public function __construct() public function __construct()
{ {
// Initialize RedisStorage for file counting $this->sqliteStorage = new SQLiteStorage(CACHE_DIR);
// 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
if (defined('S3_CACHE_ENABLED') && S3_CACHE_ENABLED === true) { if (defined('S3_CACHE_ENABLED') && S3_CACHE_ENABLED === true) {
$this->storage = new S3Storage([ $this->storage = new S3Storage([
'key' => S3_ACCESS_KEY, 'key' => S3_ACCESS_KEY,
@ -59,51 +40,30 @@ class Cache
'endpoint' => defined('S3_ENDPOINT') ? S3_ENDPOINT : null 'endpoint' => defined('S3_ENDPOINT') ? S3_ENDPOINT : null
]); ]);
} else { } else {
// Otherwise, use disk storage $this->storage = $this->sqliteStorage;
// Caso contrário, usa o storage em disco
$this->storage = new DiskStorage(CACHE_DIR);
} }
} }
/** /** Gets total number of cached files */
* 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
*/
public function getCacheFileCount(): int public function getCacheFileCount(): int
{ {
return $this->redisStorage->countCacheFiles(); return $this->sqliteStorage->countCacheFiles();
} }
/** /**
* Generates a unique ID for a URL * Generates unique cache ID from URL
* Gera um ID único para uma URL * Normalizes URL by removing protocol and www
* * Returns SHA-256 hash of normalized 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
*/ */
public function generateId($url) public function generateId($url)
{ {
// Remove protocol and www
// Remove protocolo e www
$url = preg_replace('#^https?://(www\.)?#', '', $url); $url = preg_replace('#^https?://(www\.)?#', '', $url);
// Generate unique ID using SHA-256
// Gera ID único usando SHA-256
return hash('sha256', $url); return hash('sha256', $url);
} }
/** /** Checks if cached version exists for 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
*/
public function exists($url) public function exists($url)
{ {
// If DISABLE_CACHE is active, always return false
// Se DISABLE_CACHE está ativo, sempre retorna false
if (DISABLE_CACHE) { if (DISABLE_CACHE) {
return false; return false;
} }
@ -111,17 +71,9 @@ class Cache
return $this->storage->exists($this->generateId($url)); return $this->storage->exists($this->generateId($url));
} }
/** /** Retrieves cached content for 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
*/
public function get($url) public function get($url)
{ {
// If DISABLE_CACHE is active, always return null
// Se DISABLE_CACHE está ativo, sempre retorna null
if (DISABLE_CACHE) { if (DISABLE_CACHE) {
return null; return null;
} }
@ -129,18 +81,9 @@ class Cache
return $this->storage->get($this->generateId($url)); return $this->storage->get($this->generateId($url));
} }
/** /** Stores content in cache for 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
*/
public function set($url, $content) 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) { if (DISABLE_CACHE) {
return true; return true;
} }

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