Compare commits
40 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f09a861cd1 | ||
|
7d449b5229 | ||
|
5ca8403afc | ||
|
91176050c0 | ||
|
abb1966b33 | ||
|
badd23ba7c | ||
|
602fc277dd | ||
|
8f277a648e | ||
|
4079f568ba | ||
|
30ad1d9113 | ||
|
72a5c6781f | ||
|
d921dcd115 | ||
|
9a257efd46 | ||
|
7df2056c1d | ||
|
884641e58a | ||
|
b7921a2a97 | ||
|
8b3aae2985 | ||
|
6d08a3e017 | ||
|
cfc13da108 | ||
|
c54b89eb15 | ||
|
545e8a7980 | ||
|
dc297cbff8 | ||
|
ff1e1bcc86 | ||
|
c945798d09 | ||
|
db4e512e63 | ||
|
91f58e61c7 | ||
|
9ffd8260fd | ||
|
d8568c06e9 | ||
|
3875b19817 | ||
|
c41ca87e4e | ||
|
1f5fb428a3 | ||
|
d9ef063243 | ||
|
84291c7739 | ||
|
90bcbd97fd | ||
|
1e205b6b2e | ||
|
88b37a5325 | ||
|
99ca0f420f | ||
|
f8f30b2c4d | ||
|
9a3753e80a | ||
|
5d2363ba3a |
37
.github/workflows/release.yml
vendored
|
@ -1,5 +1,5 @@
|
|||
name: 🛠️ Main
|
||||
run-name: 🚀 Deploy de versão
|
||||
run-name: 🚀 Version Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -9,49 +9,58 @@ on:
|
|||
env:
|
||||
DOCKER_REGISTRY: ghcr.io
|
||||
DOCKER_IMAGE_NAME: ${{ github.repository }}
|
||||
DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_USERNAME }}/marreta
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
name: 🐳 Build e Push
|
||||
name: 🐳 Build and Push
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: 📥 Checkout código
|
||||
- name: 📥 Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🏷️ Extrair versão da tag
|
||||
- name: 🏷️ Extract version from tag
|
||||
id: get_version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 🔧 Configurar QEMU
|
||||
- name: 🔧 Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: 🛠️ Configurar Docker Buildx
|
||||
- name: 🛠️ Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
- name: 📋 Extrair metadata Docker
|
||||
- name: 📋 Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
|
||||
images: |
|
||||
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REPOSITORY }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
|
||||
- name: 🔐 Login no Registry
|
||||
- name: 🔐 Log in to GitHub Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.DOCKER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 🏗️ Build e Push
|
||||
- name: 🔐 Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: 🏗️ Build and Push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
|
@ -63,21 +72,21 @@ jobs:
|
|||
cache-to: type=gha,mode=max
|
||||
|
||||
publish-release:
|
||||
name: 📦 Publicar Release
|
||||
name: 📦 Publish Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker-build
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: 📥 Checkout código
|
||||
- name: 📥 Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🏷️ Extrair versão da tag
|
||||
- name: 🏷️ Extract version from tag
|
||||
id: get_version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 📝 Criar Release
|
||||
- name: 📝 Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: "🎉 Release v${{ steps.get_version.outputs.VERSION }}"
|
||||
|
|
52
.gitignore
vendored
|
@ -3,48 +3,22 @@ composer.lock
|
|||
.env
|
||||
app/logs/*.log
|
||||
app/cache/*.gz
|
||||
app/cache/database/.sqlite
|
||||
TODO.md
|
||||
node_modules
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/composer,windows,macos,linux
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=composer,windows,macos,linux
|
||||
|
||||
### Composer ###
|
||||
composer.phar
|
||||
/vendor/
|
||||
|
||||
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
|
||||
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
|
||||
# composer.lock
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
|
@ -52,42 +26,22 @@ Icon
|
|||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/composer,windows,macos,linux
|
||||
*.lnk
|
58
Dockerfile
|
@ -1,50 +1,66 @@
|
|||
FROM php:8.3-fpm
|
||||
# Stage 0: Base
|
||||
FROM php:8.3-fpm AS base
|
||||
|
||||
# Install PHP dependencies and extensions
|
||||
# Instala dependências e extensões do PHP
|
||||
# Install dependencies and extensions
|
||||
RUN apt-get update && apt-get install -y \
|
||||
nginx \
|
||||
nano \
|
||||
procps \
|
||||
psmisc \
|
||||
zip \
|
||||
git \
|
||||
htop \
|
||||
cron \
|
||||
libzip-dev \
|
||||
libhiredis-dev \
|
||||
&& docker-php-ext-install zip opcache \
|
||||
&& pecl install redis \
|
||||
&& docker-php-ext-enable redis opcache
|
||||
libsqlite3-dev \
|
||||
&& docker-php-ext-install zip opcache pdo_sqlite \
|
||||
&& docker-php-ext-enable opcache \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Stage 1: Build stage
|
||||
FROM base AS builder
|
||||
|
||||
# Copy OPCache configuration
|
||||
# Copia a configuração do OPCache
|
||||
COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini
|
||||
|
||||
# Install Composer
|
||||
# Instala o Composer
|
||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
|
||||
# Copy webservice configuration
|
||||
# Copia a configuração do webservice
|
||||
COPY default.conf /etc/nginx/sites-available/default
|
||||
|
||||
RUN mkdir -p /app
|
||||
# Copy app folder
|
||||
COPY app/ /app/
|
||||
|
||||
# Install composer packages
|
||||
WORKDIR /app
|
||||
RUN composer install --no-interaction --optimize-autoloader
|
||||
|
||||
# Copy and configure initialization script permissions
|
||||
# Copia e configura permissões do script de inicialização
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
# Stage 2: Final
|
||||
FROM base
|
||||
|
||||
RUN mkdir -p /app/cache /app/logs
|
||||
# Copy necessary files from the builder stage
|
||||
COPY --from=builder /usr/local/etc/php/conf.d/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
|
||||
COPY --from=builder /usr/local/bin/composer /usr/local/bin/composer
|
||||
COPY --from=builder /app /app
|
||||
|
||||
# Copy webservice configuration
|
||||
COPY default.conf /etc/nginx/sites-available/default
|
||||
|
||||
# Copy and configure initialization script permissions
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
COPY bin/cleanup /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh \
|
||||
&& chmod +x /usr/local/bin/cleanup
|
||||
|
||||
# Create cache, database, and logs folders
|
||||
RUN mkdir -p /app/cache /app/cache/database /app/logs
|
||||
|
||||
# Configure base permissions for /app directory
|
||||
# Configura permissões base para o diretório /app
|
||||
RUN chown -R www-data:www-data /app \
|
||||
&& chmod -R 755 /app
|
||||
|
||||
# Configure Cron
|
||||
RUN touch /app/logs/cron.log
|
||||
RUN echo '0 * * * * root php "/app/bin/cleanup" >> /app/logs/cron.log 2>&1' >> /etc/crontab
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
225
README.en.md
|
@ -1,49 +1,40 @@
|
|||
# 🛠️ Marreta
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/network/members)
|
||||
[](https://github.com/manualdousuario/marreta/stargazers)
|
||||
[](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!
|
||||
|
||||

|
||||

|
||||
|
||||
Public instance at [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
|
||||
|
||||
## ✨ Features
|
||||
## ✨ What's Cool?
|
||||
|
||||
- Automatically cleans and fixes URLs
|
||||
- Automatically cleans and corrects URLs
|
||||
- Removes annoying tracking parameters
|
||||
- Forces HTTPS to keep everything secure
|
||||
- Changes user agent to avoid blocks
|
||||
- Smart DNS
|
||||
- Keeps HTML clean and optimized
|
||||
- Fixes relative URLs automatically
|
||||
- Allows custom styles
|
||||
- Changes user agent to avoid blocking
|
||||
- Leaves HTML clean and optimized
|
||||
- Fixes relative URLs on its own
|
||||
- Allows you to add your own styles and scripts
|
||||
- Removes unwanted elements
|
||||
- Cache, cache!
|
||||
- Caching, caching!
|
||||
- Blocks domains you don't want
|
||||
- Allows custom headers and cookies configuration
|
||||
- Everything with SSL/TLS
|
||||
- PHP-FPM
|
||||
- OPcache enabled
|
||||
- Direct sharing via PWA on Chrome on Android
|
||||
- Allows configuring headers and cookies your way
|
||||
- PHP-FPM and OPcache
|
||||
|
||||
## 🐳 Docker
|
||||
## 🐳 Installing with Docker
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You only need:
|
||||
- Docker and docker compose
|
||||
|
||||
### Production
|
||||
Install [Docker and Docker Compose](https://docs.docker.com/engine/install/)
|
||||
|
||||
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml`
|
||||
|
||||
If needed
|
||||
Now modify with your preferences:
|
||||
|
||||
`nano docker-compose.yml`
|
||||
|
||||
|
@ -58,188 +49,36 @@ services:
|
|||
- SITE_NAME=
|
||||
- SITE_DESCRIPTION=
|
||||
- SITE_URL=
|
||||
- LANGUAGE=
|
||||
```
|
||||
|
||||
- `SITE_NAME`: Your Marreta's name
|
||||
- `SITE_DESCRIPTION`: Tell what it's for
|
||||
- `SITE_NAME`: Name of your Marreta
|
||||
- `SITE_DESCRIPTION`: Explain what it's for
|
||||
- `SITE_URL`: Where it will run, full address with `https://`. If you change the port in docker-compose (e.g., 8080:80), you must also include the port in SITE_URL (e.g., https://yoursite:8080)
|
||||
- `DNS_SERVERS`: Which DNS servers to use `1.1.1.1, 8.8.8.8`
|
||||
- `SELENIUM_HOST`: Selenium host server:PORT (e.g., selenium-hub:4444)
|
||||
- `SELENIUM_HOST`: Server:PORT of Selenium host (e.g., selenium-hub:4444)
|
||||
- `LANGUAGE`: pt-br (Brazilian Portuguese), en (English), es (Spanish), de-de (German), ru-ru (Russian)
|
||||
|
||||
Now just run `docker compose up -d`
|
||||
|
||||
Now you can run `docker compose up -d`
|
||||
|
||||
#### Development
|
||||
|
||||
1. First, clone the project:
|
||||
```bash
|
||||
git clone https://github.com/manualdousuario/marreta/
|
||||
cd marreta
|
||||
```
|
||||
|
||||
2. Create the configuration file:
|
||||
```bash
|
||||
cp app/.env.sample app/.env
|
||||
```
|
||||
|
||||
3. Configure it your way in `app/.env`:
|
||||
```env
|
||||
SITE_NAME="Marreta"
|
||||
SITE_DESCRIPTION="Paywall hammer!"
|
||||
SITE_URL=http://localhost
|
||||
DNS_SERVERS=1.1.1.1,8.8.8.8
|
||||
LOG_LEVEL=WARNING
|
||||
SELENIUM_HOST=selenium-hub:4444
|
||||
LANGUAGE=pt-br
|
||||
```
|
||||
|
||||
4. Run everything:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Done! It will be running at `http://localhost` 🎉
|
||||
|
||||
## ⚙️ Customization
|
||||
|
||||
The configurations are organized in `data/`:
|
||||
|
||||
- `domain_rules.php`: Site-specific rules
|
||||
- `global_rules.php`: Rules that apply to all sites
|
||||
- `blocked_domains.php`: List of blocked sites
|
||||
|
||||
### Translations
|
||||
|
||||
- `/languages/`: Each language is in its ISO id (`pt-br, en, es or de-de`) and can be defined in the `LANGUAGE` environment
|
||||
|
||||
### S3 Cache
|
||||
|
||||
Cache storage support in S3. Configure the following variables in your `.env`:
|
||||
|
||||
```env
|
||||
S3_CACHE_ENABLED=true
|
||||
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=bucket_name
|
||||
S3_REGION=us-east-1
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
S3_ENDPOINT=
|
||||
```
|
||||
|
||||
Possible configurations:
|
||||
|
||||
```
|
||||
## R2
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=bucket_name
|
||||
S3_ENDPOINT=https://{TOKEN}.r2.cloudflarestorage.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
|
||||
## DigitalOcean
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=bucket_name
|
||||
S3_ENDPOINT=https://{REGION}.digitaloceanspaces.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
```
|
||||
|
||||
### Selenium Integration
|
||||
|
||||
Selenium integration for processing websites that require javascript or have more advanced protection barriers. To use this functionality, you need to set up a Selenium environment with Firefox. Add the following configuration to your `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
selenium-firefox:
|
||||
container_name: selenium-firefox
|
||||
image: selenium/node-firefox:4.27.0-20241204
|
||||
shm_size: 2gb
|
||||
environment:
|
||||
- SE_EVENT_BUS_HOST=selenium-hub
|
||||
- SE_EVENT_BUS_PUBLISH_PORT=4442
|
||||
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
|
||||
- SE_ENABLE_TRACING=false
|
||||
- SE_NODE_MAX_SESSIONS=10
|
||||
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
|
||||
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
|
||||
depends_on:
|
||||
- selenium-hub
|
||||
|
||||
selenium-hub:
|
||||
image: selenium/hub:4.27.0-20241204
|
||||
container_name: selenium-hub
|
||||
environment:
|
||||
- SE_ENABLE_TRACING=false
|
||||
- GRID_MAX_SESSION=10
|
||||
- GRID_BROWSER_TIMEOUT=10
|
||||
- GRID_TIMEOUT=10
|
||||
ports:
|
||||
- 4442:4442
|
||||
- 4443:4443
|
||||
- 4444:4444
|
||||
```
|
||||
|
||||
Important settings:
|
||||
- `shm_size`: Sets the shared memory size for Firefox (2GB recommended)
|
||||
- `SE_NODE_MAX_SESSIONS`: Maximum number of concurrent sessions per node
|
||||
- `GRID_MAX_SESSION`: Maximum number of concurrent sessions in the hub
|
||||
- `GRID_BROWSER_TIMEOUT` and `GRID_TIMEOUT`: Timeouts in seconds
|
||||
|
||||
After setting up Selenium, make sure to set the `SELENIUM_HOST` variable in your environment to point to the Selenium hub (typically `selenium-hub:4444`).
|
||||
|
||||
### Logging System
|
||||
|
||||
Logs are stored in `app/logs/app.log` with automatic 7-day rotation.
|
||||
|
||||
Log settings available in `.env` or docker:
|
||||
|
||||
```env
|
||||
LOG_LEVEL=WARNING
|
||||
```
|
||||
|
||||
Available log levels:
|
||||
- DEBUG: Detailed information for debugging
|
||||
- INFO: General operational information
|
||||
- WARNING: Warnings that deserve attention (default)
|
||||
- ERROR: Errors that don't stop operation
|
||||
- CRITICAL: Critical errors that need immediate attention
|
||||
|
||||
## 🛠️ Maintenance
|
||||
|
||||
### Logs
|
||||
|
||||
View application logs:
|
||||
```bash
|
||||
docker-compose logs app
|
||||
# or directly from the log file
|
||||
cat app/logs/app.log
|
||||
```
|
||||
|
||||
### Clearing the cache
|
||||
|
||||
When you need to clear:
|
||||
```bash
|
||||
docker-compose exec app rm -rf /app/cache/*
|
||||
```
|
||||
### More configurations:
|
||||
- Selenium: https://github.com/manualdousuario/marreta/wiki/%F0%9F%92%BB-Selenium-Hub-(Chrome-and-Firefox)
|
||||
- S3 Cache: https://github.com/manualdousuario/marreta/wiki/%F0%9F%97%83%EF%B8%8F-Cache-S3
|
||||
- Maintenance: https://github.com/manualdousuario/marreta/wiki/%F0%9F%9B%A0%EF%B8%8F-Maintenance
|
||||
|
||||
## 🚀 Integrations
|
||||
|
||||
- 🤖 **Telegram**: [Official Bot](https://t.me/leissoai_bot)
|
||||
- 🦊 **Firefox**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://addons.mozilla.org/pt-BR/firefox/addon/marreta/) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🦊 **Firefox**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://addons.mozilla.org/en-US/firefox/addon/marreta/) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🌀 **Chrome**: Extension by [Clarissa Mendes](https://claromes.com/pages/whoami) - [Download](https://chromewebstore.google.com/detail/marreta/ipelapagohjgjcgpncpbmaaacemafppe) | [Source Code](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🦋 **Bluesky**: Bot by [Joselito](https://bsky.app/profile/joseli.to) - [Profile](https://bsky.app/profile/marreta.pcdomanual.com) | [Source Code](https://github.com/manualdousuario/marreta-bot)
|
||||
- 🍎 **Apple**: [Shortcuts](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f) integration
|
||||
-
|
||||
- 🍎 **Apple**: Integration with [Shortcuts](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f)
|
||||
|
||||
---
|
||||
|
||||
Made with ❤️! If you have questions or suggestions, open an issue and we'll help! 😉
|
||||
|
||||
Thanks to the project [https://github.com/burlesco/burlesco](Burlesco) and [https://github.com/nang-dev/hover-paywalls-browser-extension/](Hover) which was used as a basis for several rules!
|
||||
Special thanks to the projects [Burlesco](https://github.com/burlesco/burlesco) and [Hover](https://github.com/nang-dev/hover-paywalls-browser-extension/) which served as the basis for many rules!
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#manualdousuario/marreta&Date)
|
||||
[](https://star-history.com/#manualdousuario/marreta&Date)
|
195
README.md
|
@ -1,7 +1,7 @@
|
|||
# 🛠️ Marreta
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.en.md)
|
||||
[](https://github.com/manualdousuario/marreta/blob/master/README.md)
|
||||
|
||||
[](https://github.com/manualdousuario/marreta/network/members)
|
||||
[](https://github.com/manualdousuario/marreta/stargazers)
|
||||
|
@ -15,35 +15,26 @@ Instancia publica em [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
|
|||
|
||||
## ✨ O que tem de legal?
|
||||
|
||||
- Limpa e arruma URLs automaticamente
|
||||
- Limpa e corrige URLs automaticamente
|
||||
- Remove parâmetros chatos de rastreamento
|
||||
- Força HTTPS pra manter tudo seguro
|
||||
- Troca de user agent pra evitar bloqueios
|
||||
- DNS esperto
|
||||
- Deixa o HTML limpinho e otimizado
|
||||
- Conserta URLs relativas sozinho
|
||||
- Permite colocar seus próprios estilos
|
||||
- Permite colocar seus próprios estilos e scripts
|
||||
- Remove elementos indesejados
|
||||
- Cache, cache!
|
||||
- Bloqueia domínios que você não quer
|
||||
- Permite configurar headers e cookies do seu jeito
|
||||
- Tudo com SSL/TLS
|
||||
- PHP-FPM
|
||||
- OPcache ligado
|
||||
- Compartilhamento direto via PWA no Chrome do Android
|
||||
- PHP-FPM e OPcache
|
||||
|
||||
## 🐳 Docker
|
||||
## 🐳 Instalando em Docker
|
||||
|
||||
### Antes de começar
|
||||
|
||||
Só precisa ter instalado:
|
||||
- Docker e docker compose
|
||||
|
||||
### Produção
|
||||
Instale [Docker e Docker Compose](https://docs.docker.com/engine/install/)
|
||||
|
||||
`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml`
|
||||
|
||||
Se necessario
|
||||
Agora modifique com suas preferencias:
|
||||
|
||||
`nano docker-compose.yml`
|
||||
|
||||
|
@ -58,182 +49,30 @@ services:
|
|||
- SITE_NAME=
|
||||
- SITE_DESCRIPTION=
|
||||
- SITE_URL=
|
||||
- LANGUAGE=
|
||||
```
|
||||
|
||||
- `SITE_NAME`: Nome do seu Marreta
|
||||
- `SITE_DESCRIPTION`: Conta pra que serve
|
||||
- `SITE_URL`: Onde vai rodar, endereço completo com `https://`. Se você alterar a porta no docker-compose (ex: 8080:80), você também deve incluir a porta no SITE_URL (ex: https://seusite:8080)
|
||||
- `DNS_SERVERS`: Quais servidores DNS usar `1.1.1.1, 8.8.8.8`
|
||||
- `SELENIUM_HOST`: Servidor:PORTA do host do Selenium (ex: selenium-hub:4444)
|
||||
-
|
||||
Agora pode rodar `docker compose up -d`
|
||||
- `LANGUAGE`: pt-br (Português Brasil), en (Inglês), es (Espanhol) ou de-de (Alemão), ru-ru (Russo)
|
||||
|
||||
Agora só rodar `docker compose up -d`
|
||||
|
||||
#### Desenvolvimento
|
||||
|
||||
1. Primeiro, clona o projeto:
|
||||
```bash
|
||||
git clone https://github.com/manualdousuario/marreta/
|
||||
cd marreta
|
||||
```
|
||||
|
||||
2. Cria o arquivo de configuração:
|
||||
```bash
|
||||
cp app/.env.sample app/.env
|
||||
```
|
||||
|
||||
3. Configura do seu jeito no `app/.env`:
|
||||
```env
|
||||
SITE_NAME="Marreta"
|
||||
SITE_DESCRIPTION="Chapéu de paywall é marreta!"
|
||||
SITE_URL=http://localhost
|
||||
DNS_SERVERS=1.1.1.1,8.8.8.8
|
||||
LOG_LEVEL=WARNING
|
||||
SELENIUM_HOST=selenium-hub:4444
|
||||
LANGUAGE=pt-br
|
||||
```
|
||||
|
||||
4. Roda tudo:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Pronto! Vai estar rodando em `http://localhost` 🎉
|
||||
|
||||
## ⚙️ Personalizando
|
||||
|
||||
As configurações estão organizadas em `data/`:
|
||||
|
||||
- `domain_rules.php`: Regras específicas para cada site
|
||||
- `global_rules.php`: Regras que se aplicam a todos os sites
|
||||
- `blocked_domains.php`: Lista de sites bloqueados
|
||||
|
||||
### Traduções
|
||||
|
||||
- `/languages/`: Cada lingua está em seu ISO id (`pt-br, en, es ou de-de`) e pode ser definida no environment `LANGUAGE`
|
||||
|
||||
### Cache S3
|
||||
|
||||
Suporte de armazenamento do cache em S3. Configure as seguintes variáveis no seu `.env`:
|
||||
|
||||
```env
|
||||
S3_CACHE_ENABLED=true
|
||||
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=nome_do_bucket
|
||||
S3_REGION=us-east-1
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
S3_ENDPOINT=
|
||||
```
|
||||
|
||||
Configurações possiveis:
|
||||
|
||||
```
|
||||
## R2
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=nome_do_bucket
|
||||
S3_ENDPOINT=https://{TOKEN}.r2.cloudflarestorage.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
|
||||
## DigitalOcean
|
||||
S3_ACCESS_KEY=access_key
|
||||
S3_SECRET_KEY=secret_key
|
||||
S3_BUCKET=nome_do_bucket
|
||||
S3_ENDPOINT=https://{REGIAO}.digitaloceanspaces.com
|
||||
S3_REGION=auto
|
||||
S3_FOLDER_=cache/
|
||||
S3_ACL=private
|
||||
```
|
||||
|
||||
### Integração com Selenium
|
||||
|
||||
Integração com Selenium para processar sites que requerem javascript ou têm algumas barreiras de proteção mais avançadas. Para usar esta funcionalidade, você precisa configurar um ambiente Selenium com Firefox. Adicione a seguinte configuração ao seu `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
selenium-firefox:
|
||||
container_name: selenium-firefox
|
||||
image: selenium/node-firefox:4.27.0-20241204
|
||||
shm_size: 2gb
|
||||
environment:
|
||||
- SE_EVENT_BUS_HOST=selenium-hub
|
||||
- SE_EVENT_BUS_PUBLISH_PORT=4442
|
||||
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
|
||||
- SE_ENABLE_TRACING=false
|
||||
- SE_NODE_MAX_SESSIONS=10
|
||||
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
|
||||
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
|
||||
depends_on:
|
||||
- selenium-hub
|
||||
|
||||
selenium-hub:
|
||||
image: selenium/hub:4.27.0-20241204
|
||||
container_name: selenium-hub
|
||||
environment:
|
||||
- SE_ENABLE_TRACING=false
|
||||
- GRID_MAX_SESSION=10
|
||||
- GRID_BROWSER_TIMEOUT=10
|
||||
- GRID_TIMEOUT=10
|
||||
ports:
|
||||
- 4442:4442
|
||||
- 4443:4443
|
||||
- 4444:4444
|
||||
```
|
||||
|
||||
Configurações importantes:
|
||||
- `shm_size`: Define o tamanho da memória compartilhada para o Firefox (2GB recomendado)
|
||||
- `SE_NODE_MAX_SESSIONS`: Número máximo de sessões simultâneas por nó
|
||||
- `GRID_MAX_SESSION`: Número máximo de sessões simultâneas no hub
|
||||
- `GRID_BROWSER_TIMEOUT` e `GRID_TIMEOUT`: Timeouts em segundos
|
||||
|
||||
Após configurar o Selenium, certifique-se de definir a variável `SELENIUM_HOST` no seu ambiente para apontar para o hub do Selenium (geralmente `selenium-hub:4444`).
|
||||
|
||||
### Sistema de Logs
|
||||
|
||||
Os logs são armazenados em `app/logs/app.log` com rotação automática a cada 7 dias.
|
||||
|
||||
Configurações de log disponíveis no `.env` ou docker:
|
||||
|
||||
```env
|
||||
LOG_LEVEL=WARNING
|
||||
```
|
||||
|
||||
Níveis de log disponíveis:
|
||||
- DEBUG: Informações detalhadas para debug
|
||||
- INFO: Informações gerais sobre operações
|
||||
- WARNING: Avisos que merecem atenção (padrão)
|
||||
- ERROR: Erros que não interrompem a operação
|
||||
- CRITICAL: Erros críticos que precisam de atenção imediata
|
||||
|
||||
## 🛠️ Manutenção
|
||||
|
||||
### Logs
|
||||
|
||||
Ver os logs da aplicação:
|
||||
```bash
|
||||
docker-compose logs app
|
||||
# ou diretamente do arquivo de log
|
||||
cat app/logs/app.log
|
||||
```
|
||||
|
||||
### Limpando o cache
|
||||
|
||||
Quando precisar limpar:
|
||||
```bash
|
||||
docker-compose exec app rm -rf /app/cache/*
|
||||
```
|
||||
### Mais configurações:
|
||||
- Selenium: https://github.com/manualdousuario/marreta/wiki/%F0%9F%92%BB-Selenium-Hub-(Chrome-and-Firefox)
|
||||
- Cache S3: https://github.com/manualdousuario/marreta/wiki/%F0%9F%97%83%EF%B8%8F-Cache-S3
|
||||
- Manutenção: https://github.com/manualdousuario/marreta/wiki/%F0%9F%9B%A0%EF%B8%8F-Maintenance
|
||||
|
||||
## 🚀 Integrações
|
||||
|
||||
- 🤖 **Telegram**: [Bot oficial](https://t.me/leissoai_bot)
|
||||
- 🦊 **Firefox**: Extensão por [Clarissa Mendes](https://claromes.com/pages/whoami) - [Baixar](https://addons.mozilla.org/pt-BR/firefox/addon/marreta/) | [Código fonte](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🌀 **Chrome**: Extensão por [Clarissa Mendes](https://claromes.com/pages/whoami) - [Baixar](https://chromewebstore.google.com/detail/marreta/ipelapagohjgjcgpncpbmaaacemafppe) | [Código fonte](https://github.com/manualdousuario/marreta-extensao)
|
||||
- 🦋 **Bluesky**: Bot por [Joselito](https://bsky.app/profile/joseli.to) - [Perfil](https://bsky.app/profile/marreta.pcdomanual.com) | [Código fonte](https://github.com/manualdousuario/marreta-bot)
|
||||
- 🍎 **Apple**: Integração ao [Atalhos](https://www.icloud.com/shortcuts/3594074b69ee4707af52ed78922d624f)
|
||||
-
|
||||
|
||||
---
|
||||
|
||||
Feito com ❤️! Se tiver dúvidas ou sugestões, abre uma issue que a gente ajuda! 😉
|
||||
|
|
|
@ -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
|
|
@ -1,43 +1,49 @@
|
|||
# Arquivo de exemplo para configuração de variáveis de ambiente
|
||||
# Copie este arquivo para .env e ajuste os valores conforme necessário
|
||||
# Sample file for environment variable configuration
|
||||
# Copy this file to .env and adjust the values as needed
|
||||
|
||||
# Nome do site exibido no cabeçalho e meta tags
|
||||
SITE_NAME=Marreta
|
||||
# Site name displayed in the header and meta tags
|
||||
SITE_NAME=Marreta
|
||||
|
||||
# Descrição do site usada em meta tags e SEO
|
||||
SITE_DESCRIPTION="Chapéu de paywall é marreta!"
|
||||
# Site description used in meta tags and SEO
|
||||
SITE_DESCRIPTION="Chapéu de paywall é marreta!"
|
||||
|
||||
# Idioma do site (opções disponíveis: pt-br, en, es, de-de)
|
||||
# pt-br = Português do Brasil
|
||||
# en = English
|
||||
# es = Español
|
||||
# de-de = German
|
||||
LANGUAGE=pt-br
|
||||
# Site language (available options: pt-br, en, es, de-de, ru-ru)
|
||||
# pt-br = Brazilian Portuguese
|
||||
# en = English
|
||||
# es = Spanish
|
||||
# de-de = German
|
||||
# ru-ru = Russian
|
||||
LANGUAGE=pt-br
|
||||
|
||||
# URL base do site (sem barra no final)
|
||||
# Use https://localhost para desenvolvimento local
|
||||
SITE_URL=https://localhost
|
||||
# Base URL of the site (without a trailing slash)
|
||||
# Use https://localhost for local development
|
||||
SITE_URL=https://localhost
|
||||
|
||||
# Lista de servidores DNS para resolução de domínios
|
||||
# Recomendado: AdGuard DNS (94.140.14.14, 94.140.15.15)
|
||||
DNS_SERVERS='94.140.14.14,94.140.15.15'
|
||||
# List of DNS servers for domain resolution
|
||||
# Recommended: AdGuard DNS (94.140.14.14, 94.140.15.15)
|
||||
DNS_SERVERS='94.140.14.14,94.140.15.15'
|
||||
|
||||
# Modo sem cache (true/false)
|
||||
# Quando ativo, desativa o cache do sistema
|
||||
DISABLE_CACHE=false
|
||||
# Disable cache mode (true/false)
|
||||
# When enabled, system caching is turned off
|
||||
DISABLE_CACHE=false
|
||||
|
||||
# Configurações de Cache S3
|
||||
S3_CACHE_ENABLED=false
|
||||
S3_ACCESS_KEY=
|
||||
S3_SECRET_KEY=
|
||||
S3_BUCKET=
|
||||
S3_REGION=us-east-1
|
||||
S3_FOLDER=cache/
|
||||
S3_ACL=private
|
||||
S3_ENDPOINT=
|
||||
# S3 Cache Settings
|
||||
S3_CACHE_ENABLED=false
|
||||
S3_ACCESS_KEY=
|
||||
S3_SECRET_KEY=
|
||||
S3_BUCKET=
|
||||
S3_REGION=us-east-1
|
||||
S3_FOLDER=cache/
|
||||
S3_ACL=private
|
||||
S3_ENDPOINT=
|
||||
|
||||
# Configurações do Selenium
|
||||
SELENIUM_HOST=localhost:4444
|
||||
# Selenium Configuration
|
||||
SELENIUM_HOST=localhost:4444
|
||||
|
||||
# Configurações de Debug
|
||||
DEBUG=true
|
||||
# Debug Settings
|
||||
DEBUG=false
|
||||
|
||||
# Cache Cleanup Settings
|
||||
# Number of days to keep cache files (*.gz)
|
||||
# If not set, no files will be cleaned
|
||||
CLEANUP_DAYS=7
|
||||
|
|
114
app/Gulpfile.js
Normal file
|
@ -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
BIN
app/assets/fonts/inter-500.ttf
Normal file
BIN
app/assets/fonts/inter-600.ttf
Normal file
BIN
app/assets/fonts/unna-400.ttf
Normal file
3
app/assets/icons/android.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-android2" viewBox="0 0 16 16">
|
||||
<path d="m10.213 1.471.691-1.26q.069-.124-.048-.192-.128-.057-.195.058l-.7 1.27A4.8 4.8 0 0 0 8.005.941q-1.032 0-1.956.404l-.7-1.27Q5.281-.037 5.154.02q-.117.069-.049.193l.691 1.259a4.25 4.25 0 0 0-1.673 1.476A3.7 3.7 0 0 0 3.5 5.02h9q0-1.125-.623-2.072a4.27 4.27 0 0 0-1.664-1.476ZM6.22 3.303a.37.37 0 0 1-.267.11.35.35 0 0 1-.263-.11.37.37 0 0 1-.107-.264.37.37 0 0 1 .107-.265.35.35 0 0 1 .263-.11q.155 0 .267.11a.36.36 0 0 1 .112.265.36.36 0 0 1-.112.264m4.101 0a.35.35 0 0 1-.262.11.37.37 0 0 1-.268-.11.36.36 0 0 1-.112-.264q0-.154.112-.265a.37.37 0 0 1 .268-.11q.155 0 .262.11a.37.37 0 0 1 .107.265q0 .153-.107.264M3.5 11.77q0 .441.311.75.311.306.76.307h.758l.01 2.182q0 .414.292.703a.96.96 0 0 0 .7.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h1.343v2.182q0 .414.292.703a.97.97 0 0 0 .71.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h.76q.436 0 .749-.308.31-.307.311-.75V5.365h-9zm10.495-6.587a.98.98 0 0 0-.702.278.9.9 0 0 0-.293.685v4.063q0 .406.293.69a.97.97 0 0 0 .702.284q.42 0 .712-.284a.92.92 0 0 0 .293-.69V6.146a.9.9 0 0 0-.293-.685 1 1 0 0 0-.712-.278m-12.702.283a1 1 0 0 1 .712-.283q.41 0 .702.283a.9.9 0 0 1 .293.68v4.063a.93.93 0 0 1-.288.69.97.97 0 0 1-.707.284 1 1 0 0 1-.712-.284.92.92 0 0 1-.293-.69V6.146q0-.396.293-.68"/>
|
||||
</svg>
|
After (image error) Size: 1.4 KiB |
4
app/assets/icons/apple.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16">
|
||||
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
|
||||
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
|
||||
</svg>
|
After (image error) Size: 1.4 KiB |
1
app/assets/icons/bookmark.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="black" d="M0 48C0 21.5 21.5 0 48 0l0 48V441.4l130.1-92.9c8.3-6 19.6-6 27.9 0L336 441.4V48H48V0H336c26.5 0 48 21.5 48 48V488c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5 37.9 507.5c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>
|
After (image error) Size: 309 B |
1
app/assets/icons/bsky.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 568 501"><path fill="currentColor" d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"></path></svg>
|
After (image error) Size: 677 B |
3
app/assets/icons/chrome.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-chrome" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M16 8a8 8 0 0 1-7.022 7.94l1.902-7.098a3 3 0 0 0 .05-1.492A3 3 0 0 0 10.237 6h5.511A8 8 0 0 1 16 8M0 8a8 8 0 0 0 7.927 8l1.426-5.321a3 3 0 0 1-.723.255 3 3 0 0 1-1.743-.147 3 3 0 0 1-1.043-.7L.633 4.876A8 8 0 0 0 0 8m5.004-.167L1.108 3.936A8.003 8.003 0 0 1 15.418 5H8.066a3 3 0 0 0-1.252.243 2.99 2.99 0 0 0-1.81 2.59M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4"/>
|
||||
</svg>
|
After (image error) Size: 525 B |
3
app/assets/icons/close.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
|
||||
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
|
||||
</svg>
|
After (image error) Size: 308 B |
1
app/assets/icons/error.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-80c-13.3 0-24 10.7-24 24s10.7 24 24 24zm-24-72h128c13.3 0 24-10.7 24-24s-10.7-24-24-24H192c-13.3 0-24 10.7-24 24s10.7 24 24 24z"/></svg>
|
After (image error) Size: 319 B |
3
app/assets/icons/firefox.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-firefox" viewBox="0 0 16 16">
|
||||
<path d="M13.384 3.408c.535.276 1.22 1.152 1.556 1.963a8 8 0 0 1 .503 3.897l-.009.077-.026.224A7.758 7.758 0 0 1 .006 8.257v-.04q.025-.545.114-1.082c.01-.074.075-.42.09-.489l.01-.051a6.6 6.6 0 0 1 1.041-2.35q.327-.465.725-.87.35-.358.758-.65a1.5 1.5 0 0 1 .26-.137c-.018.268-.04 1.553.268 1.943h.003a5.7 5.7 0 0 1 1.868-1.443 3.6 3.6 0 0 0 .021 1.896q.105.07.2.152c.107.09.226.207.454.433l.068.066.009.009a2 2 0 0 0 .213.18c.383.287.943.563 1.306.741.201.1.342.168.359.193l.004.008c-.012.193-.695.858-.933.858-2.206 0-2.564 1.335-2.564 1.335.087.997.714 1.839 1.517 2.357a4 4 0 0 0 .439.241q.114.05.228.094c.325.115.665.18 1.01.194 3.043.143 4.155-2.804 3.129-4.745v-.001a3 3 0 0 0-.731-.9 3 3 0 0 0-.571-.37l-.003-.002a2.68 2.68 0 0 1 1.87.454 3.92 3.92 0 0 0-3.396-1.983q-.116.001-.23.01l-.042.003V4.31h-.002a4 4 0 0 0-.8.14 7 7 0 0 0-.333-.314 2 2 0 0 0-.2-.152 4 4 0 0 1-.088-.383 5 5 0 0 1 1.352-.289l.05-.003c.052-.004.125-.01.205-.012C7.996 2.212 8.733.843 10.17.002l-.003.005.003-.001.002-.002h.002l.002-.002h.015a.02.02 0 0 1 .012.007 2.4 2.4 0 0 0 .206.48q.09.153.183.297c.49.774 1.023 1.379 1.543 1.968.771.874 1.512 1.715 2.036 3.02l-.001-.013a8 8 0 0 0-.786-2.353"/>
|
||||
</svg>
|
After (image error) Size: 1.3 KiB |
3
app/assets/icons/hamburguer.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5"/>
|
||||
</svg>
|
After (image error) Size: 341 B |
1
app/assets/icons/link.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="black" d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
|
After (image error) Size: 872 B |
1
app/assets/icons/marreta.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="black" d="M283.9 378.6l18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9s0 0 0 0L122 208.8s0 0 0 0L17.7 156.6C1.9 148.7-4.5 129.5 3.4 113.7l40-80C48.8 22.8 59.9 16 72 16l120 0c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zM107.6 237.4l85.9 42.9L90.9 485.5c-11.9 23.7-40.7 33.3-64.4 21.5S-6.8 466.2 5.1 442.5L107.6 237.4z"/></svg>
|
After (image error) Size: 838 B |
1
app/assets/icons/refresh.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 0 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c0 0 0 0 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.8c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/></svg>
|
After (image error) Size: 873 B |
3
app/assets/icons/telegram.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-telegram" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.287 5.906q-1.168.486-4.666 2.01-.567.225-.595.442c-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294q.39.01.868-.32 3.269-2.206 3.374-2.23c.05-.012.12-.026.166.016s.042.12.037.141c-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8 8 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629q.14.092.27.187c.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.4 1.4 0 0 0-.013-.315.34.34 0 0 0-.114-.217.53.53 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09"/>
|
||||
</svg>
|
After (image error) Size: 711 B |
1
app/assets/icons/warning.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
|
After (image error) Size: 397 B |
Before ![]() (image error) Size: 7.2 KiB After ![]() (image error) Size: 7.2 KiB ![]() ![]() |
Before ![]() (image error) Size: 4.2 KiB After ![]() (image error) Size: 4.2 KiB ![]() ![]() |
Before ![]() (image error) Size: 16 KiB After ![]() (image error) Size: 16 KiB ![]() ![]() |
BIN
app/assets/images/wall.png
Normal file
After ![]() (image error) Size: 53 KiB |
|
@ -1,130 +1,104 @@
|
|||
/**
|
||||
* JavaScript functions for form validation and error handling
|
||||
* Funções JavaScript para validação de formulário e manipulação de erros
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates the form before submission
|
||||
*
|
||||
* Checks:
|
||||
* - If URL is not empty
|
||||
* - If URL starts with http:// or https://
|
||||
* - If URL has a valid format
|
||||
*
|
||||
* @returns {boolean} True if URL is valid, False otherwise
|
||||
*
|
||||
* Valida o formulário antes do envio
|
||||
*
|
||||
* Verifica:
|
||||
* - Se a URL não está vazia
|
||||
* - Se a URL começa com http:// ou https://
|
||||
* - Se a URL tem um formato válido
|
||||
*
|
||||
* @returns {boolean} True se a URL for válida, False caso contrário
|
||||
*/
|
||||
function validateForm() {
|
||||
const urlInput = document.getElementById('url');
|
||||
const submitButton = document.querySelector('button[type="submit"]');
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
// Check if URL is not empty
|
||||
// Verifica se a URL não está vazia
|
||||
if (!url) {
|
||||
showError('Por favor, insira uma URL');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if URL starts with http:// or https://
|
||||
// Verifica se a URL começa com http:// ou https://
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
showError('A URL deve começar com http:// ou https://');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to create a URL object to validate format
|
||||
// Tenta criar um objeto URL para validar o formato
|
||||
try {
|
||||
new URL(url);
|
||||
} catch (e) {
|
||||
showError('Formato de URL inválido');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable input and button
|
||||
// Desabilita o input e o botão
|
||||
urlInput.readonly = true;
|
||||
submitButton.disabled = true;
|
||||
|
||||
// Add Tailwind disabled classes
|
||||
// Adiciona classes de disabled do Tailwind
|
||||
submitButton.classList.add('cursor-wait', 'disabled:bg-blue-400');
|
||||
submitButton.classList.remove('hover:bg-blue-700');
|
||||
|
||||
urlInput.classList.add('cursor-wait', 'disabled:bg-gray-50', 'focus:outline-none');
|
||||
|
||||
// Add loading state to button
|
||||
// Adiciona estado de loading ao botão
|
||||
submitButton.innerHTML = `
|
||||
<img src="assets/svg/search.svg" class="w-6 h-6 mr-3" alt="Search">
|
||||
Analisando...
|
||||
`;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an error message below the form
|
||||
*
|
||||
* Removes any existing error message before displaying the new one.
|
||||
* The message is displayed with an error icon and red formatting.
|
||||
*
|
||||
* @param {string} message Error message to be displayed
|
||||
*
|
||||
* Exibe uma mensagem de erro abaixo do formulário
|
||||
*
|
||||
* Remove qualquer mensagem de erro existente antes de exibir a nova.
|
||||
* A mensagem é exibida com um ícone de erro e formatação em vermelho.
|
||||
*
|
||||
* @param {string} message Mensagem de erro a ser exibida
|
||||
*/
|
||||
function showError(message) {
|
||||
const form = document.getElementById('urlForm');
|
||||
const existingError = form.querySelector('.error-message');
|
||||
|
||||
// Remove previous error message if it exists
|
||||
// Remove mensagem de erro anterior se existir
|
||||
if (existingError) {
|
||||
existingError.remove();
|
||||
}
|
||||
|
||||
// Create and add new error message
|
||||
// Cria e adiciona nova mensagem de erro
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'error-message mt-4 text-base text-red-600';
|
||||
errorDiv.innerHTML = `
|
||||
<img src="assets/svgs/error.svg" class="inline-block w-5 h-5 mr-2" alt="Error icon">
|
||||
${message}`;
|
||||
|
||||
form.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Service Worker registration for PWA functionality
|
||||
* Registers a service worker to enable offline capabilities and PWA features
|
||||
*
|
||||
* Registro do Service Worker para funcionalidade PWA
|
||||
* Registra um service worker para habilitar recursos offline e funcionalidades PWA
|
||||
*/
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/service-worker.js')
|
||||
.then(() => {
|
||||
// Service Worker registered successfully
|
||||
// Service Worker registrado com sucesso
|
||||
})
|
||||
.catch(() => {
|
||||
// Service Worker registration failed
|
||||
// Falha no registro do Service Worker
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Header toggle menus
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const integration = document.querySelector('.integration');
|
||||
const integrationToggle = document.querySelector('.integration__toggle');
|
||||
const extension = document.querySelector('.extension');
|
||||
const extensionToggle = document.querySelector('.extension__toggle');
|
||||
|
||||
// Function to close all menus
|
||||
const closeAllMenus = () => {
|
||||
integration.classList.remove('open');
|
||||
extension.classList.remove('open');
|
||||
};
|
||||
|
||||
// Function to close other menus except the one passed
|
||||
const closeOtherMenus = (exceptMenu) => {
|
||||
if (exceptMenu !== integration) {
|
||||
integration.classList.remove('open');
|
||||
}
|
||||
if (exceptMenu !== extension) {
|
||||
extension.classList.remove('open');
|
||||
}
|
||||
};
|
||||
|
||||
integrationToggle.addEventListener('click', (e) => {
|
||||
e.stopPropagation(); // Prevent click from bubbling to document
|
||||
closeOtherMenus(integration);
|
||||
integration.classList.toggle('open');
|
||||
});
|
||||
|
||||
extensionToggle.addEventListener('click', (e) => {
|
||||
e.stopPropagation(); // Prevent click from bubbling to document
|
||||
closeOtherMenus(extension);
|
||||
extension.classList.toggle('open');
|
||||
});
|
||||
|
||||
// Prevent clicks inside menus from closing them
|
||||
integration.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
extension.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Close menus when clicking outside
|
||||
document.addEventListener('click', () => {
|
||||
closeAllMenus();
|
||||
});
|
||||
|
||||
// Remove toasty elements when clicked
|
||||
document.addEventListener('click', (e) => {
|
||||
const toastyElement = e.target.closest('.toasty');
|
||||
if (toastyElement) {
|
||||
toastyElement.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle header open class when open-nav is clicked
|
||||
document.addEventListener('click', (e) => {
|
||||
const openNavElement = e.target.closest('.open-nav');
|
||||
if (openNavElement) {
|
||||
const header = document.querySelector('header');
|
||||
if (header.classList.contains('open')) {
|
||||
header.classList.remove('open');
|
||||
} else {
|
||||
header.classList.add('open');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Paste button functionality
|
||||
const pasteButton = document.getElementById('paste');
|
||||
const urlInput = document.getElementById('url');
|
||||
|
||||
if (pasteButton && urlInput) {
|
||||
pasteButton.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const clipboardText = await navigator.clipboard.readText();
|
||||
urlInput.value = clipboardText.trim();
|
||||
} catch (err) {
|
||||
console.error('Failed to read clipboard contents', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
35
app/assets/scss/_base.scss
Normal 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;
|
||||
}
|
||||
|
||||
}
|
35
app/assets/scss/_fonts.scss
Normal 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;
|
||||
}
|
43
app/assets/scss/_icons.scss
Normal file
|
@ -0,0 +1,43 @@
|
|||
@use "mixin";
|
||||
|
||||
// https://icons.getbootstrap.com/
|
||||
// https://icofont.com/icons
|
||||
|
||||
// https://jakearchibald.github.io/svgomg/
|
||||
// https://yoksel.github.io/url-encoder/
|
||||
// https://codepen.io/sosuke/pen/Pjoqqp
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: -0.125em;
|
||||
fill: currentcolor;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 100% auto;
|
||||
&--bookmark {
|
||||
background-size: auto 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@include mixin.icon('marreta', 'invert(43%) sepia(74%) saturate(2288%) hue-rotate(201deg) brightness(98%) contrast(97%)');
|
||||
@include mixin.icon('bookmark', 'invert(80%) sepia(46%) saturate(1512%) hue-rotate(340deg) brightness(106%) contrast(97%)');
|
||||
|
||||
@include mixin.icon('android', 'invert(72%) sepia(34%) saturate(778%) hue-rotate(32deg) brightness(97%) contrast(88%)');
|
||||
@include mixin.icon('apple', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
|
||||
@include mixin.icon('bsky', 'invert(47%) sepia(66%) saturate(4445%) hue-rotate(195deg) brightness(98%) contrast(104%)');
|
||||
@include mixin.icon('telegram', 'invert(61%) sepia(86%) saturate(1341%) hue-rotate(166deg) brightness(96%) contrast(85%)');
|
||||
@include mixin.icon('chrome', 'invert(40%) sepia(90%) saturate(1163%) hue-rotate(203deg) brightness(104%) contrast(92%)');
|
||||
@include mixin.icon('firefox', 'invert(57%) sepia(43%) saturate(1854%) hue-rotate(360deg) brightness(102%) contrast(106%)');
|
||||
|
||||
@include mixin.icon('link', 'invert(53%) sepia(12%) saturate(17%) hue-rotate(39deg) brightness(94%) contrast(91%)');
|
||||
@include mixin.icon('refresh', 'invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%)');
|
||||
|
||||
@include mixin.icon('error', 'invert(30%) sepia(58%) saturate(3703%) hue-rotate(336deg) brightness(90%) contrast(91%)');
|
||||
@include mixin.icon('warning', 'invert(89%) sepia(25%) saturate(5861%) hue-rotate(353deg) brightness(101%) contrast(101%)');
|
||||
|
||||
@include mixin.icon('hamburguer', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
|
||||
@include mixin.icon('close', 'invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%)');
|
||||
|
||||
@include mixin.icon('paste', 'invert(0%) sepia(21%) saturate(7425%) hue-rotate(12deg) brightness(96%) contrast(96%)');
|
23
app/assets/scss/_mixin.scss
Normal 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};
|
||||
}
|
||||
}
|
48
app/assets/scss/_root.scss
Normal file
|
@ -0,0 +1,48 @@
|
|||
@use "mixin";
|
||||
|
||||
:root {
|
||||
--font-family-sans-serif: -apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
"Helvetica Neue",
|
||||
Arial,
|
||||
"Noto Sans",
|
||||
"Liberation Sans",
|
||||
sans-serif,
|
||||
"Apple Color Emoji",
|
||||
"Segoe UI Emoji",
|
||||
"Segoe UI Symbol",
|
||||
"Noto Color Emoji";
|
||||
--font-family-monospace: SFMono-Regular,
|
||||
Menlo, Monaco,
|
||||
Consolas,
|
||||
"Liberation Mono",
|
||||
"Courier New",
|
||||
monospace;
|
||||
--font-family-inter: "inter";
|
||||
--font-family-unna: "unna";
|
||||
|
||||
--font-size: 16px;
|
||||
--font-weight: 500;
|
||||
--line-height: 160%;
|
||||
|
||||
@include mixin.create-color('marreta', #3B82F6);
|
||||
@include mixin.create-color('text', #484848);
|
||||
@include mixin.create-color('textmuted', #818181);
|
||||
@include mixin.create-color('link', #3B82F6);
|
||||
|
||||
--container_spacing: 24px;
|
||||
@include mixin.devices(desktop) {
|
||||
--container_spacing: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
641
app/assets/scss/home.scss
Normal file
|
@ -0,0 +1,641 @@
|
|||
@use "mixin";
|
||||
|
||||
body {
|
||||
padding: 42px 0 0 0;
|
||||
@include mixin.devices(desktop) {
|
||||
padding: 42px 0 52px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.toasty {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0px 10px 15px 0px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0px 20px 25px 0px rgba(0, 0, 0, 0.1);
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
color: #fff;
|
||||
z-index: 1000;
|
||||
width: auto;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.close {
|
||||
.icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
max-width: 480px;
|
||||
left: 50%;
|
||||
margin-left: -256px;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: rgb(247, 102, 97);
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: rgb(247, 152, 97);
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr 1fr;
|
||||
align-items: center;
|
||||
padding: 0 0 42px 0;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
grid-template-columns: 1fr 2fr 1fr;
|
||||
}
|
||||
|
||||
&.open {
|
||||
.extension {
|
||||
&__toggle {
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
.open-nav {
|
||||
.icon {
|
||||
&--hamburguer {
|
||||
display: none;
|
||||
}
|
||||
&--close {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.integration__menu {
|
||||
.icon {
|
||||
filter: invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.open-nav {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 501;
|
||||
padding-right: 16px;
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
&--hamburguer {
|
||||
display: block;
|
||||
}
|
||||
&--close {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@include mixin.devices(desktop) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
margin-right: 6px;
|
||||
|
||||
&--marreta {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-family-unna);
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--marreta);
|
||||
padding: var(--container_spacing) var(--container_spacing) calc(4*var(--container_spacing)) var(--container_spacing);
|
||||
z-index: 500;
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
|
||||
&> * {
|
||||
width: 100%;
|
||||
@include mixin.devices(desktop) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
opacity: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
left: initial;
|
||||
right: initial;
|
||||
bottom: initial;
|
||||
top: initial;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
align-items: normal;
|
||||
flex-direction:initial;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.24);
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
color: #333;
|
||||
font-size: initial;
|
||||
padding: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
@include mixin.devices(desktop) {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.integration {
|
||||
position: relative;
|
||||
padding-top: 32px;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&__toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255,255,255,0.5);
|
||||
padding: 0;
|
||||
@include mixin.devices(desktop) {
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: rgba(255,255,255,0.5);
|
||||
@include mixin.devices(desktop) {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__menu {
|
||||
@include mixin.devices(desktop) {
|
||||
position: absolute;
|
||||
top: 110%;
|
||||
left: 0;
|
||||
border-radius: 16px;
|
||||
background-color: #F4F4F5;
|
||||
border: 4px solid #F4F4F5;
|
||||
z-index: 10;
|
||||
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0px 10px 15px 0px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0px 20px 25px 0px rgba(0, 0, 0, 0.1);
|
||||
transition: max-height 0.8s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
border-bottom: 0;
|
||||
margin-top: 8px;
|
||||
padding: 16px 0 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
background-color: #fff;
|
||||
margin-top: 0;
|
||||
margin-bottom: 4px;
|
||||
padding: 8px 16px;
|
||||
font-size: var(--font-size);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--marreta);
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
order: 1;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
order: 2;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
order: 2;
|
||||
line-height: 1em;
|
||||
padding-left: 12px;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
order: 1;
|
||||
padding-left: 0;
|
||||
width: 140px;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
.integration__menu {
|
||||
max-height: 200px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
top: 1px;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: none;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
content: "";
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-right: 2px solid black;
|
||||
border-top: 2px solid black;
|
||||
transform: rotate(135deg);
|
||||
margin-right: 0;
|
||||
margin-left: 16px;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extension {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: relative;
|
||||
z-index: 501;
|
||||
|
||||
&__toggle {
|
||||
background-color: var(--marreta);
|
||||
border-radius: 40px;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
color: #FFF;
|
||||
font-weight: 600;
|
||||
padding: 12px 24px;
|
||||
line-height: 1.3em;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--marreta-darken);
|
||||
}
|
||||
}
|
||||
|
||||
&__menu {
|
||||
position: absolute;
|
||||
top: 110%;
|
||||
right: 0;
|
||||
border-radius: 16px;
|
||||
background-color: #F4F4F5;
|
||||
border: 4px solid #F4F4F5;
|
||||
z-index: 10;
|
||||
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0px 10px 15px 0px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0px 20px 25px 0px rgba(0, 0, 0, 0.1);
|
||||
transition: max-height 0.8s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
}
|
||||
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
padding: 8px 16px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
color: var(--marreta);
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
width: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
.extension__toggle {
|
||||
background-color: #F4F4F5;
|
||||
color: var(--textmuted);
|
||||
}
|
||||
|
||||
.extension__menu {
|
||||
max-height: 200px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
.description {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
font-family: var(--font-family-unna);
|
||||
font-size: 64px;
|
||||
line-height: 61.44px;
|
||||
text-align: center;
|
||||
color: #000;
|
||||
max-width: 512px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.walls_destroyed {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
max-width: 512px;
|
||||
margin: 22px auto;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
color: var(--textmuted);
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
|
||||
.fields {
|
||||
&::before {
|
||||
content: '';
|
||||
background-image: url(../assets/images/wall.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
width: 422px;
|
||||
height: 306px;
|
||||
position: absolute;
|
||||
top: -110px;
|
||||
right: -180px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
max-width: 470px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
|
||||
.input {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding-right: 28px;
|
||||
padding-top: 2px;
|
||||
|
||||
.icon {
|
||||
z-index: 2;
|
||||
|
||||
&--link {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 1rem;
|
||||
margin-top: -6px;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: #F4F4F5;
|
||||
padding: 16px 0 16px 44px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
.paste {
|
||||
background: rgb(244,244,245);
|
||||
background: linear-gradient(90deg, rgba(244,244,245,0) 0%, rgba(244,244,245,1) 30%, rgba(244,244,245,1) 100%);
|
||||
align-items: center;
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
padding: 0 18px 0 22px;
|
||||
right: 50px;
|
||||
cursor: pointer;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
.icon {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
position: relative;
|
||||
background-color: var(--marreta);
|
||||
border-radius: 50%;
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
border: 0;
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--marreta-darken);
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
|
||||
&--refresh,
|
||||
&--marreta {
|
||||
filter: invert(100%) sepia(32%) saturate(8%) hue-rotate(23deg) brightness(102%) contrast(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adblock {
|
||||
color: var(--textmuted);
|
||||
font-size: 13px;
|
||||
line-height: 1.2em;
|
||||
text-align: center;
|
||||
max-width: 470px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
margin: 22px auto 0 auto;
|
||||
}
|
||||
|
||||
.plus {
|
||||
z-index: 3;
|
||||
position: relative;
|
||||
background-color: rgba(244, 244, 245, 1);
|
||||
margin-left: calc(-1*var(--container_spacing));
|
||||
margin-right: calc(-1*var(--container_spacing));
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0px 38px;
|
||||
align-items: start;
|
||||
max-width: 900px;
|
||||
margin: 62px auto 0 auto;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
padding-bottom: 8px;
|
||||
margin: 0;
|
||||
|
||||
.icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: var(--textmuted);
|
||||
padding-left: 26px;
|
||||
|
||||
ol {
|
||||
padding-left: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding-right: 22px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
}
|
||||
|
||||
.add_as_app {
|
||||
margin-top: 62px;
|
||||
padding: var(--container_spacing);
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bookmarklet {
|
||||
display: none;
|
||||
|
||||
@include mixin.devices(desktop) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
border: 2px solid var(--marreta);
|
||||
color: var(--marreta);
|
||||
border-radius: 40px;
|
||||
padding: 8px 16px;
|
||||
margin-top: 16px;
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--marreta-darken);
|
||||
color: var(--marreta-darken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {}
|
10
app/assets/scss/index.scss
Normal file
|
@ -0,0 +1,10 @@
|
|||
@forward "normalize.css/normalize";
|
||||
|
||||
@use "mixin";
|
||||
@use "fonts";
|
||||
|
||||
@use "root";
|
||||
@use "base";
|
||||
@use "icons";
|
||||
|
||||
@use "home";
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#3B82F6" d="M32 32l448 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96L0 64C0 46.3 14.3 32 32 32zm0 128l448 0 0 256c0 35.3-28.7 64-64 64L96 480c-35.3 0-64-28.7-64-64l0-256zm128 80c0 8.8 7.2 16 16 16l160 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-160 0c-8.8 0-16 7.2-16 16z"/></svg>
|
Before (image error) Size: 378 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#FBBF24" d="M0 48C0 21.5 21.5 0 48 0l0 48V441.4l130.1-92.9c8.3-6 19.6-6 27.9 0L336 441.4V48H48V0H336c26.5 0 48 21.5 48 48V488c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5 37.9 507.5c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>
|
Before (image error) Size: 311 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#10B981" d="M240 32a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM192 48a32 32 0 1 1 0 64 32 32 0 1 1 0-64zm-32 80c17.7 0 32 14.3 32 32l8 0c13.3 0 24 10.7 24 24l0 16c0 1.7-.2 3.4-.5 5.1C280.3 229.6 320 286.2 320 352c0 88.4-71.6 160-160 160S0 440.4 0 352c0-65.8 39.7-122.4 96.5-146.9c-.4-1.6-.5-3.3-.5-5.1l0-16c0-13.3 10.7-24 24-24l8 0c0-17.7 14.3-32 32-32zm0 320a96 96 0 1 0 0-192 96 96 0 1 0 0 192zm192-96c0-25.9-5.1-50.5-14.4-73.1c16.9-32.9 44.8-59.1 78.9-73.9c-.4-1.6-.5-3.3-.5-5.1l0-16c0-13.3 10.7-24 24-24l8 0c0-17.7 14.3-32 32-32s32 14.3 32 32l8 0c13.3 0 24 10.7 24 24l0 16c0 1.7-.2 3.4-.5 5.1C600.3 229.6 640 286.2 640 352c0 88.4-71.6 160-160 160c-62 0-115.8-35.3-142.4-86.9c9.3-22.5 14.4-47.2 14.4-73.1zm224 0a96 96 0 1 0 -192 0 96 96 0 1 0 192 0zM368 0a32 32 0 1 1 0 64 32 32 0 1 1 0-64zm80 48a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
Before (image error) Size: 910 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#6B7280" d="M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"/></svg>
|
Before (image error) Size: 558 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#EF4444" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-80c-13.3 0-24 10.7-24 24s10.7 24 24 24zm-24-72h128c13.3 0 24-10.7 24-24s-10.7-24-24-24H192c-13.3 0-24 10.7-24 24s10.7 24 24 24z"/></svg>
|
Before (image error) Size: 321 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#3B82F6" d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
|
Before (image error) Size: 874 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#3B82F6" d="M283.9 378.6l18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9s0 0 0 0L122 208.8s0 0 0 0L17.7 156.6C1.9 148.7-4.5 129.5 3.4 113.7l40-80C48.8 22.8 59.9 16 72 16l120 0c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zM107.6 237.4l85.9 42.9L90.9 485.5c-11.9 23.7-40.7 33.3-64.4 21.5S-6.8 466.2 5.1 442.5L107.6 237.4z"/></svg>
|
Before (image error) Size: 840 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#3B82F6" d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 0 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c0 0 0 0 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.8c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/></svg>
|
Before (image error) Size: 875 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFFFFF" d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
|
Before (image error) Size: 328 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FBBF24" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
|
Before (image error) Size: 399 B |
0
app/cache/database/.gitkeep
vendored
Normal file
|
@ -5,7 +5,8 @@
|
|||
"php-curl-class/php-curl-class": "^11.0",
|
||||
"php-webdriver/webdriver": "^1.15",
|
||||
"monolog/monolog": "^3.8.1",
|
||||
"nikic/fast-route": "^1.3"
|
||||
"nikic/fast-route": "^1.3",
|
||||
"league/climate": "^3.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
@ -1,84 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Main configuration file
|
||||
* Arquivo de configuração principal
|
||||
*
|
||||
* This file contains all global system settings, including:
|
||||
* Este arquivo contém todas as configurações globais do sistema, incluindo:
|
||||
*
|
||||
* - Environment variables loading / Carregamento de variáveis de ambiente
|
||||
* - System constants definition / Definições de constantes do sistema
|
||||
* - Security settings / Configurações de segurança
|
||||
* - Bot and user agent settings / Configurações de bots e user agents
|
||||
* - Blocked domains list / Lista de domínios bloqueados
|
||||
* - S3 cache settings / Configurações de cache S3
|
||||
* System configuration manager
|
||||
* - Loads and validates environment variables
|
||||
* - Defines global constants for system settings
|
||||
* - Manages security rules and external service configs
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
try {
|
||||
// Initialize environment variables
|
||||
// Inicializa as variáveis de ambiente
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
// Validate required fields
|
||||
// Valida campos obrigatórios
|
||||
$dotenv->required([
|
||||
'SITE_NAME',
|
||||
'SITE_DESCRIPTION',
|
||||
'SITE_URL'
|
||||
])->notEmpty();
|
||||
|
||||
// Custom URL validation
|
||||
// Validação personalizada de URL
|
||||
// Validate URL format
|
||||
if (!filter_var($_ENV['SITE_URL'], FILTER_VALIDATE_URL)) {
|
||||
throw new Exception('SITE_URL must be a valid URL');
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic system settings
|
||||
* Configurações básicas do sistema
|
||||
*/
|
||||
// Core system settings
|
||||
define('SITE_NAME', $_ENV['SITE_NAME']);
|
||||
define('SITE_DESCRIPTION', $_ENV['SITE_DESCRIPTION']);
|
||||
define('SITE_URL', $_ENV['SITE_URL']);
|
||||
define('CLEANUP_DAYS', $_ENV['CLEANUP_DAYS'] ?? 0);
|
||||
|
||||
// Optional settings with default values
|
||||
// Configurações opcionais com valores padrão
|
||||
// Optional settings with defaults
|
||||
define('DNS_SERVERS', $_ENV['DNS_SERVERS'] ?? '1.1.1.1, 8.8.8.8');
|
||||
define('DISABLE_CACHE', isset($_ENV['DISABLE_CACHE']) ?
|
||||
filter_var($_ENV['DISABLE_CACHE'], FILTER_VALIDATE_BOOLEAN) : false);
|
||||
define('DISABLE_CACHE', isset($_ENV['DISABLE_CACHE']) ? filter_var($_ENV['DISABLE_CACHE'], FILTER_VALIDATE_BOOLEAN) : false);
|
||||
define('SELENIUM_HOST', $_ENV['SELENIUM_HOST'] ?? 'localhost:4444');
|
||||
define('CACHE_DIR', __DIR__ . '/cache');
|
||||
define('LANGUAGE', $_ENV['LANGUAGE'] ?? 'pt-br');
|
||||
|
||||
/**
|
||||
* Redis settings
|
||||
* Configurações do Redis
|
||||
*/
|
||||
define('REDIS_HOST', $_ENV['REDIS_HOST'] ?? 'localhost');
|
||||
define('REDIS_PORT', $_ENV['REDIS_PORT'] ?? 6379);
|
||||
define('REDIS_PREFIX', $_ENV['REDIS_PREFIX'] ?? 'marreta:');
|
||||
|
||||
/**
|
||||
* Logging settings
|
||||
* Configurações de log
|
||||
*/
|
||||
define('LOG_LEVEL', $_ENV['LOG_LEVEL'] ?? 'WARNING'); // Available: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
// Logging configuration
|
||||
define('LOG_LEVEL', $_ENV['LOG_LEVEL'] ?? 'WARNING'); // DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
define('LOG_DAYS_TO_KEEP', 7);
|
||||
|
||||
/**
|
||||
* S3 Cache settings
|
||||
* Configurações de Cache S3
|
||||
*/
|
||||
define('S3_CACHE_ENABLED', isset($_ENV['S3_CACHE_ENABLED']) ?
|
||||
filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN) : false);
|
||||
// S3 cache configuration
|
||||
define('S3_CACHE_ENABLED', isset($_ENV['S3_CACHE_ENABLED']) ? filter_var($_ENV['S3_CACHE_ENABLED'], FILTER_VALIDATE_BOOLEAN) : false);
|
||||
|
||||
if (S3_CACHE_ENABLED) {
|
||||
// Validate required S3 settings when S3 cache is enabled
|
||||
// Valida configurações obrigatórias do S3 quando o cache S3 está ativado
|
||||
$dotenv->required([
|
||||
'S3_ACCESS_KEY',
|
||||
'S3_SECRET_KEY',
|
||||
|
@ -94,10 +62,7 @@ try {
|
|||
define('S3_ENDPOINT', $_ENV['S3_ENDPOINT'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load system configurations
|
||||
* Carrega as configurações do sistema
|
||||
*/
|
||||
// Load security rules
|
||||
define('BLOCKED_DOMAINS', require __DIR__ . '/data/blocked_domains.php');
|
||||
define('DOMAIN_RULES', require __DIR__ . '/data/domain_rules.php');
|
||||
define('GLOBAL_RULES', require __DIR__ . '/data/global_rules.php');
|
||||
|
|
|
@ -2,17 +2,13 @@
|
|||
|
||||
/**
|
||||
* List of blocked domains
|
||||
* Lista de domínios bloqueados
|
||||
*
|
||||
* Defines domains that cannot be accessed by the system
|
||||
* due to usage policies or technical restrictions
|
||||
*
|
||||
* Define os domínios que não podem ser acessados pelo sistema
|
||||
* por questões de política de uso ou restrições técnicas
|
||||
*/
|
||||
return [
|
||||
// News sites / Sites de notícias
|
||||
//-- Content behind login access / Conteúdo fica atrás de um acesso de login
|
||||
// News sites
|
||||
//-- Content behind login access/hard paywall
|
||||
'wsj.com',
|
||||
'piaui.folha.uol.com.br',
|
||||
'economist.com',
|
||||
|
@ -31,14 +27,16 @@ return [
|
|||
'mittelbayerische.de',
|
||||
'josimarfootball.com',
|
||||
'nordsee-zeitung.de',
|
||||
//-- Technical access blocking / Bloqueio técnico de acesso ao conteúdo
|
||||
'zorgvisie.nl',
|
||||
// List of common blocked sites to avoid unnecessary requests
|
||||
//-- Technical access blocking
|
||||
'bloomberg.com',
|
||||
'sportskeeda.com',
|
||||
'kansascity.com',
|
||||
'fastcompany.com',
|
||||
'expressnews.com',
|
||||
'nydailynews.com',
|
||||
// Tracking services / Serviços de rastreamento
|
||||
//-- Tracking services
|
||||
'metaffiliation.com',
|
||||
'google-analytics.com',
|
||||
'googletagmanager.com',
|
||||
|
@ -59,7 +57,7 @@ return [
|
|||
'fullstory.com',
|
||||
'heap.io',
|
||||
'clearbrain.com',
|
||||
// Social networks / Redes sociais
|
||||
//-- Social networks
|
||||
'facebook.com',
|
||||
'instagram.com',
|
||||
'twitter.com',
|
||||
|
@ -73,7 +71,7 @@ return [
|
|||
'redd.it',
|
||||
'bsky.app',
|
||||
'threads.net',
|
||||
// Streaming services / Serviços de streaming
|
||||
//-- Streaming services
|
||||
'netflix.com',
|
||||
'hulu.com',
|
||||
'disneyplus.com',
|
||||
|
@ -81,32 +79,32 @@ return [
|
|||
'spotify.com',
|
||||
'youtube.com',
|
||||
'twitch.tv',
|
||||
// E-commerce sites / Sites de comércio eletrônico
|
||||
//-- E-commerce sites
|
||||
'amazon.com',
|
||||
'ebay.com',
|
||||
'aliexpress.com',
|
||||
'mercadolivre.com.br',
|
||||
'shopify.com',
|
||||
// File sharing / Compartilhamento de arquivos
|
||||
//-- File sharing
|
||||
'mega.nz',
|
||||
'mediafire.com',
|
||||
'wetransfer.com',
|
||||
'dropbox.com',
|
||||
'torrent9.pe',
|
||||
'thepiratebay.org',
|
||||
// Adult sites / Sites adultos
|
||||
//-- Adult sites
|
||||
'pornhub.com',
|
||||
'xvideos.com',
|
||||
'xnxx.com',
|
||||
'onlyfans.com',
|
||||
'privacy.com.br',
|
||||
'fatalmodel.com',
|
||||
// Betting and gaming / Apostas e jogos
|
||||
//-- Betting and gaming
|
||||
'bet365.com',
|
||||
'betfair.com',
|
||||
'pokerstars.com',
|
||||
'casino.com',
|
||||
// Other popular sites / Outros sites populares
|
||||
//-- Other popular sites
|
||||
'github.com',
|
||||
'stackoverflow.com',
|
||||
'wikipedia.org',
|
||||
|
|
|
@ -2,38 +2,31 @@
|
|||
|
||||
/**
|
||||
* Specific rule configurations for individual domains
|
||||
* Configurações específicas de regras para domínios individuais
|
||||
*
|
||||
* This file contains custom rules for specific sites, allowing
|
||||
* system behavior adjustment for each domain individually.
|
||||
*
|
||||
* Este arquivo contém regras personalizadas para sites específicos, permitindo
|
||||
* ajustar o comportamento do sistema para cada domínio individualmente.
|
||||
*
|
||||
* Domain rule structure / Estrutura das regras por domínio:
|
||||
* - userAgent: Define custom User-Agent for the domain / Define um User-Agent personalizado para o domínio
|
||||
* - headers: Custom HTTP headers for requests / Headers HTTP personalizados para requisições
|
||||
* - idElementRemove: Array of HTML IDs to be removed / Array de IDs HTML que devem ser removidos da página
|
||||
* - classElementRemove: Array of HTML classes to be removed / Array de classes HTML que devem ser removidas
|
||||
* - scriptTagRemove: Array of scripts to be removed (partial match) / Array de scripts que devem ser removidos (partial match)
|
||||
* - cookies: Associative array of cookies to be set (null removes cookie) / Array associativo de cookies a serem definidos (null remove o cookie)
|
||||
* - classAttrRemove: Array of classes to be removed from elements / Array de classes a serem removidas de elementos
|
||||
* - customCode: String containing custom JavaScript code / String contendo código JavaScript personalizado
|
||||
* - customStyle: String containing custom CSS code / String contendo código CSS personalizado
|
||||
* - excludeGlobalRules: Associative array of global rules to exclude for this domain / Array associativo de regras globais a serem excluídas para este domínio
|
||||
* Example / Exemplo:
|
||||
* - userAgent: Define custom User-Agent for the domain
|
||||
* - headers: Custom HTTP headers for requests
|
||||
* - idElementRemove: Array of HTML IDs to be removed
|
||||
* - classElementRemove: Array of HTML classes to be removed
|
||||
* - scriptTagRemove: Array of scripts to be removed (partial match)
|
||||
* - cookies: Associative array of cookies to be set (null removes cookie)
|
||||
* - classAttrRemove: Array of classes to be removed from elements
|
||||
* - customCode: String containing custom JavaScript code
|
||||
* - customStyle: String containing custom CSS code
|
||||
* - excludeGlobalRules: Associative array of global rules to exclude for this domain
|
||||
* Example:
|
||||
* 'excludeGlobalRules' => [
|
||||
* 'scriptTagRemove' => ['gtm.js', 'ga.js'], // Excludes specific scripts from global rules / Exclui scripts específicos das regras globais
|
||||
* 'classElementRemove' => ['subscription'] // Excludes specific classes from global rules / Exclui classes específicas das regras globais
|
||||
* 'scriptTagRemove' => ['gtm.js', 'ga.js'],
|
||||
* 'classElementRemove' => ['subscription']
|
||||
* ]
|
||||
* - fetchStrategies: String indicating which fetch strategy to use. Available values: / String indicando qual estratégia de fetch usar. Valores disponíveis:
|
||||
* - fetchContent: Use standard fetch with domain rules / Usa fetch padrão com regras do domínio
|
||||
* - fetchFromWaybackMachine: Try to fetch from Internet Archive / Tenta buscar do Internet Archive
|
||||
* - fetchFromSelenium: Use Selenium for extraction / Usa Selenium para extração
|
||||
* - socialReferrers: Add random social media headers / Adiciona headers randomicos de redes sociais
|
||||
* - fromGoogleBot: Adds simulation of request coming from Google Bot / Adiciona simulação de requisição vinda do Google Bot
|
||||
* - removeElementsByTag: Remove specific elements via DOM / Remove elementos especificos via DOM
|
||||
* - removeCustomAttr: Remove custom attributes from elements / Remove custom attributes from elements
|
||||
* - fetchStrategies: String indicating which fetch strategy to use. Available values:
|
||||
* - fetchContent: Use standard fetch with domain rules
|
||||
* - fetchFromWaybackMachine: Try to fetch from Internet Archive
|
||||
* - fetchFromSelenium: Use Selenium for extraction
|
||||
* - socialReferrers: Add random social media headers
|
||||
* - fromGoogleBot: Adds simulation of request coming from Google Bot
|
||||
* - removeElementsByTag: Remove specific elements via DOM
|
||||
* - removeCustomAttr: Remove custom attributes from elements
|
||||
*/
|
||||
return [
|
||||
'nsctotal.com.br' => [
|
||||
|
@ -47,6 +40,12 @@ return [
|
|||
'removeElementsByTag' => ['style'],
|
||||
'removeCustomAttr' => ['hidden','data-*']
|
||||
],
|
||||
'wired.com' => [
|
||||
'scriptTagRemove' => ['.js'],
|
||||
],
|
||||
'newyorker.com' => [
|
||||
'scriptTagRemove' => ['.js'],
|
||||
],
|
||||
'globo.com' => [
|
||||
'idElementRemove' => ['cookie-banner-lgpd', 'paywall-cpt', 'mc-read-more-wrapper', 'paywall-cookie-content', 'paywall-cpt'],
|
||||
'classElementRemove' => ['banner-lgpd', 'article-related-link__title', 'article-related-link__picture', 'paywall-denied', 'banner-subscription'],
|
||||
|
@ -71,6 +70,18 @@ return [
|
|||
'classElementRemove' => ['leaderboard__container'],
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
],
|
||||
'lepoint.fr' => [
|
||||
'classElementRemove' => ['paywall'],
|
||||
],
|
||||
'gamestar.de' => [
|
||||
'classElementRemove' => ['plus-teaser'],
|
||||
'classAttrRemove' => ['plus-'],
|
||||
'idElementRemove' => ['commentReload']
|
||||
],
|
||||
'heise.de' => [
|
||||
'classAttrRemove' => ['curtain__purchase-container'],
|
||||
'removeElementsByTag' => ['a-gift']
|
||||
],
|
||||
'fortune.com' => [
|
||||
'classElementRemove' => ['latest-popular-module','own','drawer-menu'],
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
|
@ -137,6 +148,21 @@ return [
|
|||
'paywall_access' => 'true'
|
||||
]
|
||||
],
|
||||
'ftm.nl' => [
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
'removeCustomAttr' => ['dialog','iframe'],
|
||||
'classElementRemove' => ['modal'],
|
||||
'scriptTagRemove' => ['footer.min','diffuser.js','insight.ftm.nl'],
|
||||
'classAttrRemove' => ['hasBlockingOverlay', 'localstorage']
|
||||
],
|
||||
'denikn.cz' => [
|
||||
'idElementRemove' => ['e_lock__hard']
|
||||
],
|
||||
'dtest.cz' => [
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
'classAttrRemove' => ['is-hidden-compare'],
|
||||
'classElementRemove' => ['cc-window']
|
||||
],
|
||||
'uol.com.br' => [
|
||||
'scriptTagRemove' => ['me.jsuol.com.br', 'c.jsuol.com.br'],
|
||||
'classElementRemove' => ['header-top-wrapper'],
|
||||
|
@ -159,7 +185,8 @@ return [
|
|||
]
|
||||
],
|
||||
'theverge.com' => [
|
||||
'fetchStrategies' => 'fetchFromSelenium',
|
||||
'scriptTagRemove' => 'zephr',
|
||||
'classElementRemove' => 'zephr'
|
||||
],
|
||||
'economist.com' => [
|
||||
'cookies' => [
|
||||
|
@ -186,7 +213,7 @@ return [
|
|||
'fromGoogleBot' => true
|
||||
],
|
||||
'nytimes.com' => [
|
||||
'idElementRemove' => ['gateway-content','site-index'],
|
||||
'idElementRemove' => ['gateway-content','site-index','complianceOverlay'],
|
||||
'customCode' => '
|
||||
setTimeout(function() {
|
||||
const walk = document.createTreeWalker(
|
||||
|
|
|
@ -2,23 +2,12 @@
|
|||
|
||||
/**
|
||||
* Global rule configurations applied to all domains
|
||||
* Configurações globais de regras aplicadas a todos os domínios
|
||||
*
|
||||
* This file defines rules that are applied by default to all sites,
|
||||
* organized into categories for better maintenance and understanding.
|
||||
*
|
||||
* Este arquivo define regras que são aplicadas por padrão a todos os sites,
|
||||
* organizadas em categorias para melhor manutenção e compreensão.
|
||||
*
|
||||
* Note: These rules can be overridden or disabled for specific domains
|
||||
* using the 'excludeGlobalRules' configuration in domain_rules.php
|
||||
*
|
||||
* Nota: Estas regras podem ser sobrescritas ou desativadas para domínios específicos
|
||||
* usando a configuração 'excludeGlobalRules' em domain_rules.php
|
||||
*/
|
||||
return [
|
||||
// HTML classes to be removed from all pages
|
||||
// Classes HTML a serem removidas de todas as páginas
|
||||
// Classes to be removed from all pages:
|
||||
'classElementRemove' => [
|
||||
'subscription',
|
||||
'subscriber-content',
|
||||
|
@ -40,8 +29,7 @@ return [
|
|||
'signup-overlay',
|
||||
'onesignal-slidedown-container'
|
||||
],
|
||||
// Scripts to be removed from all pages
|
||||
// Scripts a serem removidos de todas as páginas
|
||||
// Scripts to be removed from all pages:
|
||||
'scriptTagRemove' => [
|
||||
'gtm.js',
|
||||
'ga.js',
|
||||
|
@ -80,6 +68,8 @@ return [
|
|||
'getblue.io',
|
||||
'smartocto.com',
|
||||
'cdn.pn.vg',
|
||||
'static.vocstatic.com'
|
||||
'static.vocstatic.com',
|
||||
'recaptcha',
|
||||
'intercom'
|
||||
]
|
||||
];
|
||||
|
|
2
app/dist/css/style.css
vendored
Normal file
1
app/dist/css/style.css.map
vendored
Normal file
BIN
app/dist/fonts/inter-500.eot
vendored
Normal file
BIN
app/dist/fonts/inter-500.ttf
vendored
Normal file
BIN
app/dist/fonts/inter-500.woff
vendored
Normal file
BIN
app/dist/fonts/inter-500.woff2
vendored
Normal file
BIN
app/dist/fonts/inter-600.eot
vendored
Normal file
BIN
app/dist/fonts/inter-600.ttf
vendored
Normal file
BIN
app/dist/fonts/inter-600.woff
vendored
Normal file
BIN
app/dist/fonts/inter-600.woff2
vendored
Normal file
BIN
app/dist/fonts/unna-400.eot
vendored
Normal file
BIN
app/dist/fonts/unna-400.ttf
vendored
Normal file
BIN
app/dist/fonts/unna-400.woff
vendored
Normal file
BIN
app/dist/fonts/unna-400.woff2
vendored
Normal file
1
app/dist/icons/android.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-android2"><path d="m10.213 1.471.691-1.26q.069-.124-.048-.192-.128-.057-.195.058l-.7 1.27A4.8 4.8 0 0 0 8.005.941q-1.032 0-1.956.404l-.7-1.27Q5.281-.037 5.154.02q-.117.069-.049.193l.691 1.259a4.25 4.25 0 0 0-1.673 1.476A3.7 3.7 0 0 0 3.5 5.02h9q0-1.125-.623-2.072a4.27 4.27 0 0 0-1.664-1.476ZM6.22 3.303a.37.37 0 0 1-.267.11.35.35 0 0 1-.263-.11.37.37 0 0 1-.107-.264.37.37 0 0 1 .107-.265.35.35 0 0 1 .263-.11q.155 0 .267.11a.36.36 0 0 1 .112.265.36.36 0 0 1-.112.264m4.101 0a.35.35 0 0 1-.262.11.37.37 0 0 1-.268-.11.36.36 0 0 1-.112-.264q0-.154.112-.265a.37.37 0 0 1 .268-.11q.155 0 .262.11a.37.37 0 0 1 .107.265q0 .153-.107.264M3.5 11.77q0 .441.311.75.311.306.76.307h.758l.01 2.182q0 .414.292.703a.96.96 0 0 0 .7.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h1.343v2.182q0 .414.292.703a.97.97 0 0 0 .71.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h.76q.436 0 .749-.308.31-.307.311-.75V5.365h-9zm10.495-6.587a.98.98 0 0 0-.702.278.9.9 0 0 0-.293.685v4.063q0 .406.293.69a.97.97 0 0 0 .702.284q.42 0 .712-.284a.92.92 0 0 0 .293-.69V6.146a.9.9 0 0 0-.293-.685 1 1 0 0 0-.712-.278m-12.702.283a1 1 0 0 1 .712-.283q.41 0 .702.283a.9.9 0 0 1 .293.68v4.063a.93.93 0 0 1-.288.69.97.97 0 0 1-.707.284 1 1 0 0 1-.712-.284.92.92 0 0 1-.293-.69V6.146q0-.396.293-.68"/></svg>
|
After (image error) Size: 1.3 KiB |
1
app/dist/icons/apple.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple"><path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/><path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/></svg>
|
After (image error) Size: 1.4 KiB |
1
app/dist/icons/bookmark.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M0 48C0 21.5 21.5 0 48 0v441.4l130.1-92.9c8.3-6 19.6-6 27.9 0l130 92.9V48H48V0h288c26.5 0 48 21.5 48 48v440c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5l-154.1 110c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>
|
After (image error) Size: 289 B |
1
app/dist/icons/bsky.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 568 501"><path fill="currentColor" d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748 114.875 19.55 144.097 84.31 80.986 149.07-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"/></svg>
|
After (image error) Size: 668 B |
1
app/dist/icons/chrome.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-chrome"><path fill-rule="evenodd" d="M16 8a8 8 0 0 1-7.022 7.94l1.902-7.098a3 3 0 0 0 .05-1.492A3 3 0 0 0 10.237 6h5.511A8 8 0 0 1 16 8M0 8a8 8 0 0 0 7.927 8l1.426-5.321a3 3 0 0 1-.723.255 3 3 0 0 1-1.743-.147 3 3 0 0 1-1.043-.7L.633 4.876A8 8 0 0 0 0 8m5.004-.167L1.108 3.936A8.003 8.003 0 0 1 15.418 5H8.066a3 3 0 0 0-1.252.243 2.99 2.99 0 0 0-1.81 2.59M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4"/></svg>
|
After (image error) Size: 501 B |
1
app/dist/icons/close.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg"><path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/></svg>
|
After (image error) Size: 284 B |
1
app/dist/icons/error.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm-40-176h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-80c-13.3 0-24 10.7-24 24s10.7 24 24 24zm-24-72h128c13.3 0 24-10.7 24-24s-10.7-24-24-24H192c-13.3 0-24 10.7-24 24s10.7 24 24 24z"/></svg>
|
After (image error) Size: 305 B |
1
app/dist/icons/firefox.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-firefox"><path d="M13.384 3.408c.535.276 1.22 1.152 1.556 1.963a8 8 0 0 1 .503 3.897l-.009.077-.026.224A7.758 7.758 0 0 1 .006 8.257v-.04q.025-.545.114-1.082c.01-.074.075-.42.09-.489l.01-.051a6.6 6.6 0 0 1 1.041-2.35q.327-.465.725-.87.35-.358.758-.65a1.5 1.5 0 0 1 .26-.137c-.018.268-.04 1.553.268 1.943h.003a5.7 5.7 0 0 1 1.868-1.443 3.6 3.6 0 0 0 .021 1.896q.105.07.2.152c.107.09.226.207.454.433l.068.066.009.009a2 2 0 0 0 .213.18c.383.287.943.563 1.306.741.201.1.342.168.359.193l.004.008c-.012.193-.695.858-.933.858-2.206 0-2.564 1.335-2.564 1.335.087.997.714 1.839 1.517 2.357a4 4 0 0 0 .439.241q.114.05.228.094c.325.115.665.18 1.01.194 3.043.143 4.155-2.804 3.129-4.745v-.001a3 3 0 0 0-.731-.9 3 3 0 0 0-.571-.37l-.003-.002a2.68 2.68 0 0 1 1.87.454 3.92 3.92 0 0 0-3.396-1.983q-.116.001-.23.01l-.042.003V4.31h-.002a4 4 0 0 0-.8.14 7 7 0 0 0-.333-.314 2 2 0 0 0-.2-.152 4 4 0 0 1-.088-.383 5 5 0 0 1 1.352-.289l.05-.003c.052-.004.125-.01.205-.012C7.996 2.212 8.733.843 10.17.002l-.003.005.003-.001.002-.002h.002l.002-.002h.015a.02.02 0 0 1 .012.007 2.4 2.4 0 0 0 .206.48q.09.153.183.297c.49.774 1.023 1.379 1.543 1.968.771.874 1.512 1.715 2.036 3.02l-.001-.013a8 8 0 0 0-.786-2.353"/></svg>
|
After (image error) Size: 1.3 KiB |
1
app/dist/icons/hamburguer.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list"><path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5"/></svg>
|
After (image error) Size: 317 B |
1
app/dist/icons/link.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6 31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0l112.3-112.3zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5 50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5l112.2-112.3c31.5-31.5 82.5-31.5 114 0 27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
|
After (image error) Size: 856 B |
1
app/dist/icons/marreta.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="m283.9 378.6 18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9-85.7-42.9-104.3-52.2c-15.8-7.9-22.2-27.1-14.3-42.9l40-80C48.8 22.8 59.9 16 72 16h120c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zm-204.6-39.5 85.9 42.9L90.9 485.5C79 509.2 50.2 518.8 26.5 507s-33.3-40.8-21.4-64.5l102.5-205.1z"/></svg>
|
After (image error) Size: 805 B |
3
app/dist/icons/paste.svg
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33333 2.66667H3.45333C3.72667 3.44 4.46 4 5.33333 4H6.66667C7.53333 4 8.26667 3.44 8.54667 2.66667H8.66667C9.58 2.66667 10.3733 3.28 10.6067 4.16667C10.7 4.52667 11.06 4.74 11.42 4.64667C11.7733 4.55333 11.9933 4.19333 11.9 3.83333C11.52 2.36 10.1933 1.33333 8.67333 1.33333H8.56C8.28667 0.56 7.54667 0 6.67333 0H5.34C4.47333 0 3.72667 0.56 3.45333 1.33333H3.34C1.49333 1.33333 0 2.82667 0 4.66667V12.6667C0 14.5067 1.49333 16 3.33333 16H5.33333C5.7 16 6 15.7 6 15.3333C6 14.9667 5.7 14.6667 5.33333 14.6667H3.33333C2.23333 14.6667 1.33333 13.7667 1.33333 12.6667V4.66667C1.33333 3.56667 2.23333 2.66667 3.33333 2.66667ZM5.33333 1.33333H6.66667C7.03333 1.33333 7.33333 1.63333 7.33333 2C7.33333 2.36667 7.03333 2.66667 6.66667 2.66667H5.33333C4.96667 2.66667 4.66667 2.36667 4.66667 2C4.66667 1.63333 4.96667 1.33333 5.33333 1.33333ZM13.3333 6H10C8.52667 6 7.33333 7.19333 7.33333 8.66667V13.3333C7.33333 14.8067 8.52667 16 10 16H13.3333C14.8067 16 16 14.8067 16 13.3333V8.66667C16 7.19333 14.8067 6 13.3333 6ZM14.6667 13.3333C14.6667 14.0667 14.0667 14.6667 13.3333 14.6667H10C9.26667 14.6667 8.66667 14.0667 8.66667 13.3333V8.66667C8.66667 7.93333 9.26667 7.33333 10 7.33333H13.3333C14.0667 7.33333 14.6667 7.93333 14.6667 8.66667V13.3333ZM13.3333 9.33333C13.3333 9.7 13.0333 10 12.6667 10H10.6667C10.3 10 10 9.7 10 9.33333C10 8.96667 10.3 8.66667 10.6667 8.66667H12.6667C13.0333 8.66667 13.3333 8.96667 13.3333 9.33333ZM13.3333 12C13.3333 12.3667 13.0333 12.6667 12.6667 12.6667H10.6667C10.3 12.6667 10 12.3667 10 12C10 11.6333 10.3 11.3333 10.6667 11.3333H12.6667C13.0333 11.3333 13.3333 11.6333 13.3333 12Z" fill="black"/>
|
||||
</svg>
|
After (image error) Size: 1.7 KiB |
1
app/dist/icons/refresh.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8 62.5-62.5 163.8-62.5 226.3 0l17.1 17.2H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h111.9c17.7 0 32-14.3 32-32V80c0-17.7-14.3-32-32-32s-32 14.3-32 32v35.2l-17.5-17.6c-87.5-87.5-229.3-87.5-316.8 0-24.4 24.4-42 53.1-52.8 83.8-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2-4 4-6.7 8.8-8.1 14-.3 1.2-.6 2.5-.8 3.8-.3 1.7-.4 3.4-.4 5.1V432c0 17.7 14.3 32 32 32s32-14.3 32-32v-35.1l17.6 17.5c87.5 87.4 229.3 87.4 316.7 0 24.4-24.4 42.1-53.1 52.9-83.8 5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8-62.5 62.5-163.8 62.5-226.3 0l-.1-.1-17.1-17H160c17.7 0 32-14.3 32-32s-14.3-32-32-32H48.4c-1.6 0-3.2.1-4.8.3s-3.1.5-4.6 1z"/></svg>
|
After (image error) Size: 796 B |
1
app/dist/icons/telegram.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-telegram"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.287 5.906q-1.168.486-4.666 2.01-.567.225-.595.442c-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294q.39.01.868-.32 3.269-2.206 3.374-2.23c.05-.012.12-.026.166.016s.042.12.037.141c-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8 8 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629q.14.092.27.187c.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.4 1.4 0 0 0-.013-.315.34.34 0 0 0-.114-.217.53.53 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09"/></svg>
|
After (image error) Size: 687 B |
1
app/dist/icons/warning.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7.2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8.2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24v112c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0-64 0 32 32 0 1 0 64 0z"/></svg>
|
After (image error) Size: 380 B |
BIN
app/dist/images/opengraph.png
vendored
Normal file
After ![]() (image error) Size: 6.4 KiB |
BIN
app/dist/images/opengraph.webp
vendored
Normal file
After ![]() (image error) Size: 5.9 KiB |
BIN
app/dist/images/pwa/192x192.png
vendored
Normal file
After ![]() (image error) Size: 2.2 KiB |
BIN
app/dist/images/pwa/192x192.webp
vendored
Normal file
After ![]() (image error) Size: 1.4 KiB |
BIN
app/dist/images/pwa/512x512.png
vendored
Normal file
After ![]() (image error) Size: 6.9 KiB |
BIN
app/dist/images/pwa/512x512.webp
vendored
Normal file
After ![]() (image error) Size: 3.9 KiB |
BIN
app/dist/images/wall.png
vendored
Normal file
After ![]() (image error) Size: 45 KiB |
BIN
app/dist/images/wall.webp
vendored
Normal file
After ![]() (image error) Size: 4.5 KiB |
2
app/dist/js/scripts.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
"serviceWorker"in navigator&&window.addEventListener("load",()=>{navigator.serviceWorker.register("/service-worker.js").then(()=>{}).catch(()=>{})}),document.addEventListener("DOMContentLoaded",function(){let t=document.querySelector(".integration");var e=document.querySelector(".integration__toggle");let o=document.querySelector(".extension");var n=document.querySelector(".extension__toggle");let r=e=>{e!==t&&t.classList.remove("open"),e!==o&&o.classList.remove("open")};e.addEventListener("click",e=>{e.stopPropagation(),r(t),t.classList.toggle("open")}),n.addEventListener("click",e=>{e.stopPropagation(),r(o),o.classList.toggle("open")}),t.addEventListener("click",e=>{e.stopPropagation()}),o.addEventListener("click",e=>{e.stopPropagation()}),document.addEventListener("click",()=>{t.classList.remove("open"),o.classList.remove("open")}),document.addEventListener("click",e=>{e=e.target.closest(".toasty");e&&e.remove()}),document.addEventListener("click",e=>{e.target.closest(".open-nav")&&((e=document.querySelector("header")).classList.contains("open")?e.classList.remove("open"):e.classList.add("open"))});e=document.getElementById("paste");let a=document.getElementById("url");e&&a&&e.addEventListener("click",async e=>{e.preventDefault();try{var t=await navigator.clipboard.readText();a.value=t.trim()}catch(e){console.error("Failed to read clipboard contents",e)}})});
|
||||
//# sourceMappingURL=scripts.js.map
|
1
app/dist/js/scripts.js.map
vendored
Normal file
|
@ -1,53 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Inc;
|
||||
|
||||
use Inc\Cache\CacheStorageInterface;
|
||||
use Inc\Cache\DiskStorage;
|
||||
use Inc\Cache\S3Storage;
|
||||
use Inc\Cache\RedisStorage;
|
||||
use Inc\Cache\SQLiteStorage;
|
||||
|
||||
/**
|
||||
* Class responsible for system cache management
|
||||
* Classe responsável pelo gerenciamento de cache do sistema
|
||||
*
|
||||
* This class implements functionalities to store and retrieve
|
||||
* cached content, supporting multiple storage backends (disk or S3).
|
||||
* The cache is organized by URLs converted to unique IDs using SHA-256.
|
||||
* Content is compressed using gzip to save space.
|
||||
*
|
||||
* Esta classe implementa funcionalidades para armazenar e recuperar
|
||||
* conteúdo em cache, suportando múltiplos backends de armazenamento (disco ou S3).
|
||||
* O cache é organizado por URLs convertidas em IDs únicos usando SHA-256.
|
||||
* O conteúdo é comprimido usando gzip para economizar espaço.
|
||||
* System cache management with multiple storage backends (disk/S3)
|
||||
* Uses SHA-256 hashed URLs as unique identifiers
|
||||
* Implements gzip compression for space efficiency
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
/**
|
||||
* @var CacheStorageInterface Storage implementation for cache
|
||||
* @var CacheStorageInterface Implementação de storage para o cache
|
||||
*/
|
||||
/** @var CacheStorageInterface Cache storage implementation */
|
||||
private $storage;
|
||||
|
||||
/**
|
||||
* @var RedisStorage Redis instance for file counting
|
||||
* @var RedisStorage Instância do Redis para contagem de arquivos
|
||||
*/
|
||||
private $redisStorage;
|
||||
/** @var SQLiteStorage SQLite instance for file counting */
|
||||
private $sqliteStorage;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* Construtor da classe
|
||||
*
|
||||
* Initializes appropriate storage based on configuration
|
||||
* Inicializa o storage apropriado baseado na configuração
|
||||
* Initializes storage based on configuration
|
||||
* Uses S3Storage if configured and enabled
|
||||
* Defaults to SQLiteStorage otherwise (which delegates to DiskStorage)
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Initialize RedisStorage for file counting
|
||||
// Inicializa o RedisStorage para contagem de arquivos
|
||||
$this->redisStorage = new RedisStorage(CACHE_DIR);
|
||||
|
||||
// If S3 is configured and active, use S3Storage
|
||||
// Se S3 está configurado e ativo, usa S3Storage
|
||||
$this->sqliteStorage = new SQLiteStorage(CACHE_DIR);
|
||||
|
||||
if (defined('S3_CACHE_ENABLED') && S3_CACHE_ENABLED === true) {
|
||||
$this->storage = new S3Storage([
|
||||
'key' => S3_ACCESS_KEY,
|
||||
|
@ -59,51 +40,30 @@ class Cache
|
|||
'endpoint' => defined('S3_ENDPOINT') ? S3_ENDPOINT : null
|
||||
]);
|
||||
} else {
|
||||
// Otherwise, use disk storage
|
||||
// Caso contrário, usa o storage em disco
|
||||
$this->storage = new DiskStorage(CACHE_DIR);
|
||||
$this->storage = $this->sqliteStorage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the count of cached files
|
||||
* Obtém a contagem de arquivos em cache
|
||||
*
|
||||
* @return int Number of files in cache / Número de arquivos em cache
|
||||
*/
|
||||
/** Gets total number of cached files */
|
||||
public function getCacheFileCount(): int
|
||||
{
|
||||
return $this->redisStorage->countCacheFiles();
|
||||
return $this->sqliteStorage->countCacheFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique ID for a URL
|
||||
* Gera um ID único para uma URL
|
||||
*
|
||||
* @param string $url URL for which the ID will be generated / URL para qual será gerado o ID
|
||||
* @return string SHA-256 hash of the normalized URL / Hash SHA-256 da URL normalizada
|
||||
* Generates unique cache ID from URL
|
||||
* Normalizes URL by removing protocol and www
|
||||
* Returns SHA-256 hash of normalized URL
|
||||
*/
|
||||
public function generateId($url)
|
||||
{
|
||||
// Remove protocol and www
|
||||
// Remove protocolo e www
|
||||
$url = preg_replace('#^https?://(www\.)?#', '', $url);
|
||||
// Generate unique ID using SHA-256
|
||||
// Gera ID único usando SHA-256
|
||||
return hash('sha256', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cache exists for a given URL
|
||||
* Verifica se existe cache para uma determinada URL
|
||||
*
|
||||
* @param string $url URL to check / URL a ser verificada
|
||||
* @return bool True if cache exists, False otherwise / True se existir cache, False caso contrário
|
||||
*/
|
||||
/** Checks if cached version exists for URL */
|
||||
public function exists($url)
|
||||
{
|
||||
// If DISABLE_CACHE is active, always return false
|
||||
// Se DISABLE_CACHE está ativo, sempre retorna false
|
||||
if (DISABLE_CACHE) {
|
||||
return false;
|
||||
}
|
||||
|
@ -111,17 +71,9 @@ class Cache
|
|||
return $this->storage->exists($this->generateId($url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cached content for a URL
|
||||
* Recupera o conteúdo em cache de uma URL
|
||||
*
|
||||
* @param string $url URL of the content to retrieve / URL do conteúdo a ser recuperado
|
||||
* @return string|null Cached content or null if it doesn't exist / Conteúdo em cache ou null se não existir
|
||||
*/
|
||||
/** Retrieves cached content for URL */
|
||||
public function get($url)
|
||||
{
|
||||
// If DISABLE_CACHE is active, always return null
|
||||
// Se DISABLE_CACHE está ativo, sempre retorna null
|
||||
if (DISABLE_CACHE) {
|
||||
return null;
|
||||
}
|
||||
|
@ -129,18 +81,9 @@ class Cache
|
|||
return $this->storage->get($this->generateId($url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores content in cache for a URL
|
||||
* Armazena conteúdo em cache para uma URL
|
||||
*
|
||||
* @param string $url URL associated with the content / URL associada ao conteúdo
|
||||
* @param string $content Content to be cached / Conteúdo a ser armazenado em cache
|
||||
* @return bool True if cache was saved successfully, False otherwise / True se o cache foi salvo com sucesso, False caso contrário
|
||||
*/
|
||||
/** Stores content in cache for URL */
|
||||
public function set($url, $content)
|
||||
{
|
||||
// If DISABLE_CACHE is active, don't generate cache
|
||||
// Se DISABLE_CACHE está ativo, não gera cache
|
||||
if (DISABLE_CACHE) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,39 +3,27 @@
|
|||
namespace Inc\Cache;
|
||||
|
||||
/**
|
||||
* Interface for cache storage implementations
|
||||
* Interface para implementações de armazenamento de cache
|
||||
*
|
||||
* This interface defines the required methods for any cache storage implementation.
|
||||
* Esta interface define os métodos necessários para qualquer implementação de armazenamento de cache.
|
||||
* Defines the contract for cache storage implementations
|
||||
*/
|
||||
interface CacheStorageInterface
|
||||
{
|
||||
/**
|
||||
* Checks if cache exists for a given ID
|
||||
* Verifica se existe cache para um determinado ID
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
|
||||
* Checks if cached content exists for given ID
|
||||
* @param string $id Unique cache identifier
|
||||
*/
|
||||
public function exists(string $id): bool;
|
||||
|
||||
/**
|
||||
* Retrieves cached content
|
||||
* Recupera o conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
|
||||
* Retrieves cached content by ID
|
||||
* @return string|null Cached content or null if missing
|
||||
*/
|
||||
public function get(string $id): ?string;
|
||||
|
||||
/**
|
||||
* Stores content in cache
|
||||
* Armazena conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @param string $content Content to be stored / Conteúdo a ser armazenado
|
||||
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
|
||||
* Stores content in cache with specified ID
|
||||
* @param string $id Cache entry identifier
|
||||
* @param string $content Content to store
|
||||
* @return bool Storage success status
|
||||
*/
|
||||
public function set(string $id, string $content): bool;
|
||||
}
|
||||
}
|
|
@ -3,25 +3,19 @@
|
|||
namespace Inc\Cache;
|
||||
|
||||
/**
|
||||
* Disk-based cache storage implementation
|
||||
* Implementação de armazenamento de cache em disco
|
||||
*
|
||||
* This class implements file-based caching using gzip compression
|
||||
* Esta classe implementa cache baseado em arquivos usando compressão gzip
|
||||
* Disk-based cache storage
|
||||
* Implements file-based caching using gzip compression
|
||||
*/
|
||||
class DiskStorage implements CacheStorageInterface
|
||||
{
|
||||
/**
|
||||
* @var string Directory where cache files will be stored
|
||||
* @var string Diretório onde os arquivos de cache serão armazenados
|
||||
* @var string Directory for cache files
|
||||
*/
|
||||
private $cacheDir;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* Construtor da classe
|
||||
*
|
||||
* @param string $cacheDir Base directory for cache storage / Diretório base para armazenamento do cache
|
||||
* @param string $cacheDir Base directory for cache storage
|
||||
*/
|
||||
public function __construct(string $cacheDir)
|
||||
{
|
||||
|
@ -33,10 +27,8 @@ class DiskStorage implements CacheStorageInterface
|
|||
|
||||
/**
|
||||
* Checks if cache exists for a given ID
|
||||
* Verifica se existe cache para um determinado ID
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
|
||||
* @param string $id Cache ID
|
||||
* @return bool True if cache exists, false otherwise
|
||||
*/
|
||||
public function exists(string $id): bool
|
||||
{
|
||||
|
@ -46,10 +38,8 @@ class DiskStorage implements CacheStorageInterface
|
|||
|
||||
/**
|
||||
* Retrieves cached content
|
||||
* Recupera o conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
|
||||
* @param string $id Cache ID
|
||||
* @return string|null Cached content or null if not found
|
||||
*/
|
||||
public function get(string $id): ?string
|
||||
{
|
||||
|
@ -69,11 +59,9 @@ class DiskStorage implements CacheStorageInterface
|
|||
|
||||
/**
|
||||
* Stores content in cache
|
||||
* Armazena conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @param string $content Content to be stored / Conteúdo a ser armazenado
|
||||
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
|
||||
* @param string $id Cache ID
|
||||
* @param string $content Content to be stored
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
public function set(string $id, string $content): bool
|
||||
{
|
||||
|
@ -86,4 +74,4 @@ class DiskStorage implements CacheStorageInterface
|
|||
|
||||
return file_put_contents($cachePath, $compressedContent) !== false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Inc\Cache;
|
||||
|
||||
use Redis;
|
||||
|
||||
/**
|
||||
* Redis-based cache storage implementation
|
||||
* Implementação de armazenamento de cache baseado em Redis
|
||||
*
|
||||
* This class provides cache storage and file counting functionality using Redis
|
||||
* Esta classe fornece armazenamento de cache e funcionalidade de contagem de arquivos usando Redis
|
||||
*
|
||||
* @property \Redis|null $redis Redis client instance
|
||||
*/
|
||||
class RedisStorage implements CacheStorageInterface
|
||||
{
|
||||
/**
|
||||
* @var \Redis|null Redis client instance
|
||||
* @var \Redis|null Instância do cliente Redis
|
||||
*/
|
||||
private $redis;
|
||||
|
||||
/**
|
||||
* @var string Cache directory for file counting
|
||||
* @var string Diretório de cache para contagem de arquivos
|
||||
*/
|
||||
private $cacheDir;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* Construtor da classe
|
||||
*
|
||||
* @param string $cacheDir Base directory for cache storage / Diretório base para armazenamento do cache
|
||||
*/
|
||||
public function __construct(string $cacheDir)
|
||||
{
|
||||
$this->cacheDir = $cacheDir;
|
||||
|
||||
// Try to initialize Redis connection
|
||||
// Tenta inicializar conexão Redis
|
||||
try {
|
||||
/** @var \Redis $redis */
|
||||
$this->redis = new \Redis();
|
||||
$this->redis->connect(REDIS_HOST, REDIS_PORT, 2.5);
|
||||
$this->redis->setOption(\Redis::OPT_PREFIX, REDIS_PREFIX);
|
||||
} catch (\Exception $e) {
|
||||
// If it fails, set redis to null
|
||||
// Se falhar, define redis como null
|
||||
$this->redis = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of files in the cache directory
|
||||
* Conta o número de arquivos no diretório de cache
|
||||
*
|
||||
* @return int Number of files in the cache directory / Número de arquivos no diretório de cache
|
||||
*/
|
||||
public function countCacheFiles(): int
|
||||
{
|
||||
// Key to store file count in Redis
|
||||
// Chave para armazenar a contagem de arquivos no Redis
|
||||
$cacheCountKey = 'cache_file_count';
|
||||
|
||||
// If Redis is available
|
||||
// Se Redis estiver disponível
|
||||
if ($this->redis !== null) {
|
||||
// Check if the key exists and has a value
|
||||
// Verifica se a chave existe e tem valor
|
||||
/** @var string|false $cachedCount */
|
||||
$cachedCount = $this->redis->get($cacheCountKey);
|
||||
if ($cachedCount !== false) {
|
||||
return (int)$cachedCount;
|
||||
}
|
||||
}
|
||||
|
||||
// If Redis is not available or key is empty, count .gz files
|
||||
// Se Redis não estiver disponível ou chave vazia, conta arquivos .gz
|
||||
$fileCount = 0;
|
||||
$iterator = new \FilesystemIterator($this->cacheDir);
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'gz') {
|
||||
$fileCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// If Redis is available, save the count
|
||||
// Se Redis estiver disponível, salva a contagem
|
||||
if ($this->redis !== null) {
|
||||
$this->redis->set($cacheCountKey, $fileCount);
|
||||
}
|
||||
|
||||
return $fileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file count in Redis
|
||||
* Atualiza a contagem de arquivos no Redis
|
||||
*
|
||||
* @param int $count Number of files / Número de arquivos
|
||||
*/
|
||||
public function updateCacheFileCount(int $count): void
|
||||
{
|
||||
if ($this->redis !== null) {
|
||||
$this->redis->set('cache_file_count', $count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cache exists for a given ID
|
||||
* Verifica se existe cache para um determinado ID
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
|
||||
*/
|
||||
public function exists(string $id): bool
|
||||
{
|
||||
return $this->redis !== null ? $this->redis->exists($id) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cached content
|
||||
* Recupera o conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
|
||||
*/
|
||||
public function get(string $id): ?string
|
||||
{
|
||||
if ($this->redis === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var string|false $content */
|
||||
$content = $this->redis->get($id);
|
||||
return $content === false ? null : $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores content in cache
|
||||
* Armazena conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @param string $content Content to be stored / Conteúdo a ser armazenado
|
||||
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
|
||||
*/
|
||||
public function set(string $id, string $content): bool
|
||||
{
|
||||
// If Redis is not available, return false
|
||||
// Se Redis não estiver disponível, retorna false
|
||||
if ($this->redis === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When saving a new file, update the count
|
||||
// Ao salvar um novo arquivo, atualiza a contagem
|
||||
/** @var bool $result */
|
||||
$result = $this->redis->set($id, $content);
|
||||
|
||||
if ($result) {
|
||||
// Increment file count in Redis
|
||||
// Incrementa a contagem de arquivos no Redis
|
||||
/** @var string|false $currentCount */
|
||||
$currentCount = $this->redis->get('cache_file_count') ?: 0;
|
||||
$this->redis->set('cache_file_count', $currentCount + 1);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -7,42 +7,33 @@ use Aws\Exception\AwsException;
|
|||
|
||||
/**
|
||||
* AWS S3-based cache storage implementation
|
||||
* Implementação de armazenamento de cache baseado em AWS S3
|
||||
*
|
||||
* This class provides cache storage functionality using Amazon S3 or compatible services
|
||||
* Esta classe fornece funcionalidade de armazenamento de cache usando Amazon S3 ou serviços compatíveis
|
||||
* Provides cache storage functionality using Amazon S3 or compatible services
|
||||
*/
|
||||
class S3Storage implements CacheStorageInterface
|
||||
{
|
||||
/**
|
||||
* @var S3Client AWS S3 Client
|
||||
* @var S3Client Cliente AWS S3
|
||||
*/
|
||||
private $s3Client;
|
||||
|
||||
/**
|
||||
* @var string S3 bucket name
|
||||
* @var string Nome do bucket S3
|
||||
*/
|
||||
private $bucket;
|
||||
|
||||
/**
|
||||
* @var string Prefix for objects in the bucket (optional)
|
||||
* @var string Prefixo para os objetos no bucket (opcional)
|
||||
*/
|
||||
private $prefix;
|
||||
|
||||
/**
|
||||
* @var string ACL for S3 objects
|
||||
* @var string ACL para os objetos no S3
|
||||
*/
|
||||
private $acl;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* Construtor da classe
|
||||
*
|
||||
* @param array $config AWS S3 configuration / Configuração do AWS S3
|
||||
* @param array $config AWS S3 configuration
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
|
@ -56,11 +47,8 @@ class S3Storage implements CacheStorageInterface
|
|||
];
|
||||
|
||||
// Add custom endpoint if provided
|
||||
// Adiciona endpoint personalizado se fornecido
|
||||
if (!empty($config['endpoint'])) {
|
||||
$clientConfig['endpoint'] = $config['endpoint'];
|
||||
// Use path-style endpoints when a custom endpoint is provided
|
||||
// Use endpoints estilo path quando um endpoint personalizado é fornecido
|
||||
$clientConfig['use_path_style_endpoint'] = true;
|
||||
}
|
||||
|
||||
|
@ -73,10 +61,8 @@ class S3Storage implements CacheStorageInterface
|
|||
|
||||
/**
|
||||
* Generates the complete object key in S3
|
||||
* Gera a chave completa do objeto no S3
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return string Complete S3 object key / Chave completa do objeto no S3
|
||||
* @param string $id Cache ID
|
||||
* @return string Complete S3 object key
|
||||
*/
|
||||
private function getObjectKey(string $id): string
|
||||
{
|
||||
|
@ -85,10 +71,8 @@ class S3Storage implements CacheStorageInterface
|
|||
|
||||
/**
|
||||
* Checks if cache exists for a given ID
|
||||
* Verifica se existe cache para um determinado ID
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return bool True if cache exists, false otherwise / True se o cache existir, false caso contrário
|
||||
* @param string $id Cache ID
|
||||
* @return bool True if cache exists, false otherwise
|
||||
*/
|
||||
public function exists(string $id): bool
|
||||
{
|
||||
|
@ -98,17 +82,14 @@ class S3Storage implements CacheStorageInterface
|
|||
$this->getObjectKey($id)
|
||||
);
|
||||
} catch (AwsException $e) {
|
||||
// Log error if needed / Registra erro se necessário
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cached content
|
||||
* Recupera o conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @return string|null Cached content or null if not found / Conteúdo em cache ou null se não encontrado
|
||||
* @param string $id Cache ID
|
||||
* @return string|null Cached content or null if not found
|
||||
*/
|
||||
public function get(string $id): ?string
|
||||
{
|
||||
|
@ -136,11 +117,9 @@ class S3Storage implements CacheStorageInterface
|
|||
|
||||
/**
|
||||
* Stores content in cache
|
||||
* Armazena conteúdo em cache
|
||||
*
|
||||
* @param string $id Cache ID / ID do cache
|
||||
* @param string $content Content to be stored / Conteúdo a ser armazenado
|
||||
* @return bool True if successful, false otherwise / True se bem sucedido, false caso contrário
|
||||
* @param string $id Cache ID
|
||||
* @param string $content Content to be stored
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
public function set(string $id, string $content): bool
|
||||
{
|
||||
|
@ -156,13 +135,12 @@ class S3Storage implements CacheStorageInterface
|
|||
'Body' => $compressedContent,
|
||||
'ACL' => $this->acl,
|
||||
'ContentEncoding' => 'gzip',
|
||||
'CacheControl' => 'max-age=31536000' // 1 year / 1 ano
|
||||
'CacheControl' => 'max-age=31536000' // 1 year
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (AwsException $e) {
|
||||
// Log error if needed / Registra erro se necessário
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
177
app/inc/Cache/SQLiteStorage.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Inc\Cache;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* SQLite-based cache storage implementation
|
||||
* Provides file counting functionality using SQLite
|
||||
* Delegates actual cache storage to DiskStorage
|
||||
*/
|
||||
class SQLiteStorage implements CacheStorageInterface
|
||||
{
|
||||
/**
|
||||
* @var PDO|null SQLite connection
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* @var string Cache directory for file counting
|
||||
*/
|
||||
private $cacheDir;
|
||||
|
||||
/**
|
||||
* @var string Path to SQLite database file
|
||||
*/
|
||||
private $dbPath;
|
||||
|
||||
/**
|
||||
* @var DiskStorage Disk storage for cache entries
|
||||
*/
|
||||
private $diskStorage;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param string $cacheDir Base directory for cache storage
|
||||
*/
|
||||
public function __construct(string $cacheDir)
|
||||
{
|
||||
$this->cacheDir = $cacheDir;
|
||||
$this->diskStorage = new DiskStorage($cacheDir);
|
||||
|
||||
// Ensure database directory exists
|
||||
$dbDir = $cacheDir . '/database';
|
||||
if (!is_dir($dbDir)) {
|
||||
mkdir($dbDir, 0755, true);
|
||||
}
|
||||
|
||||
$this->dbPath = $dbDir . '/.sqlite';
|
||||
|
||||
// Try to initialize SQLite connection
|
||||
try {
|
||||
$this->db = new PDO('sqlite:' . $this->dbPath);
|
||||
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// Create tables if they don't exist
|
||||
$this->initDatabase();
|
||||
|
||||
// If database file was just created, count cache files
|
||||
if (!file_exists($this->dbPath) || filesize($this->dbPath) < 1024) {
|
||||
$this->countCacheFiles();
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$this->db = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database tables
|
||||
*/
|
||||
private function initDatabase(): void
|
||||
{
|
||||
$this->db->exec("
|
||||
CREATE TABLE IF NOT EXISTS stats (
|
||||
key TEXT PRIMARY KEY,
|
||||
value INTEGER NOT NULL
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of files in the cache directory
|
||||
* @return int Number of files in the cache directory
|
||||
*/
|
||||
public function countCacheFiles(): int
|
||||
{
|
||||
if ($this->db !== null) {
|
||||
try {
|
||||
$stmt = $this->db->query("SELECT value FROM stats WHERE key = 'count'");
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result) {
|
||||
return (int)$result['value'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// Continue to count files if query fails
|
||||
}
|
||||
}
|
||||
|
||||
$fileCount = 0;
|
||||
$iterator = new \FilesystemIterator($this->cacheDir);
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'gz') {
|
||||
$fileCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->db !== null) {
|
||||
$this->updateCacheFileCount($fileCount);
|
||||
}
|
||||
|
||||
return $fileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file count in SQLite
|
||||
* @param int $count Number of files
|
||||
*/
|
||||
public function updateCacheFileCount(int $count): void
|
||||
{
|
||||
if ($this->db !== null) {
|
||||
try {
|
||||
$stmt = $this->db->prepare("
|
||||
INSERT OR REPLACE INTO stats (key, value)
|
||||
VALUES ('count', :count)
|
||||
");
|
||||
$stmt->bindParam(':count', $count, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
// Silently fail if update fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cache exists for a given ID
|
||||
* Delegates to DiskStorage
|
||||
* @param string $id Cache ID
|
||||
* @return bool True if cache exists, false otherwise
|
||||
*/
|
||||
public function exists(string $id): bool
|
||||
{
|
||||
return $this->diskStorage->exists($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cached content
|
||||
* Delegates to DiskStorage
|
||||
* @param string $id Cache ID
|
||||
* @return string|null Cached content or null if not found
|
||||
*/
|
||||
public function get(string $id): ?string
|
||||
{
|
||||
return $this->diskStorage->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores content in cache
|
||||
* Delegates to DiskStorage and updates file count
|
||||
* @param string $id Cache ID
|
||||
* @param string $content Content to be stored
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
public function set(string $id, string $content): bool
|
||||
{
|
||||
$result = $this->diskStorage->set($id, $content);
|
||||
|
||||
if ($result) {
|
||||
// Increment cache file count
|
||||
$currentCount = $this->countCacheFiles();
|
||||
$this->updateCacheFileCount($currentCount + 1);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -1,27 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Inc;
|
||||
|
||||
/**
|
||||
* Language Management Class
|
||||
* Classe de Gerenciamento de Idiomas
|
||||
*
|
||||
* This class handles the loading and retrieval of language-specific strings
|
||||
* Esta classe lida com o carregamento e recuperação de strings específicas do idioma
|
||||
*
|
||||
* Features / Funcionalidades:
|
||||
* - Language initialization / Inicialização de idioma
|
||||
* - Translation retrieval / Recuperação de traduções
|
||||
* - Message handling / Manipulação de mensagens
|
||||
* - Fallback language support / Suporte a idioma de fallback
|
||||
* Manages language translations and localization
|
||||
* Loads language files based on system configuration
|
||||
* Provides fallback to default language on missing resources
|
||||
*/
|
||||
class Language {
|
||||
private static $translations = [];
|
||||
private static $currentLanguage = 'pt-br';
|
||||
|
||||
/**
|
||||
* Initialize the language system
|
||||
* Inicializa o sistema de idiomas
|
||||
*
|
||||
* @param string $language Language code (e.g., 'en', 'pt-br') / Código do idioma (ex: 'en', 'pt-br')
|
||||
* Initializes language resources
|
||||
* @param string $language ISO language code (e.g., 'en', 'pt-br')
|
||||
*/
|
||||
public static function init($language = 'pt-br') {
|
||||
self::$currentLanguage = strtolower($language);
|
||||
|
@ -30,31 +22,25 @@ class Language {
|
|||
if (file_exists($langFile)) {
|
||||
self::$translations = require $langFile;
|
||||
} else {
|
||||
// Fallback to pt-br if language file doesn't exist
|
||||
// Volta para pt-br se o arquivo de idioma não existir
|
||||
// Fallback to default language
|
||||
self::$currentLanguage = 'pt-br';
|
||||
self::$translations = require __DIR__ . '/../languages/pt-br.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a translation by key
|
||||
* Obtém uma tradução por chave
|
||||
*
|
||||
* @param string $key Translation key / Chave da tradução
|
||||
* @param string $default Default value if key not found / Valor padrão se a chave não for encontrada
|
||||
* @return string Translation text / Texto traduzido
|
||||
* Retrieves translation for specified key
|
||||
* @param string $key Translation identifier
|
||||
* @param string $default Fallback value if key not found
|
||||
*/
|
||||
public static function get($key, $default = '') {
|
||||
return self::$translations[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a message by key
|
||||
* Obtém uma mensagem por chave
|
||||
*
|
||||
* @param string $key Message key / Chave da mensagem
|
||||
* @return array Message data (message and type) / Dados da mensagem (mensagem e tipo)
|
||||
* Gets structured message data
|
||||
* @param string $key Message identifier
|
||||
* @return array Message content and type
|
||||
*/
|
||||
public static function getMessage($key) {
|
||||
return self::$translations['messages'][$key] ?? [
|
||||
|
@ -63,13 +49,8 @@ class Language {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current language code
|
||||
* Obtém o código do idioma atual
|
||||
*
|
||||
* @return string Current language code / Código do idioma atual
|
||||
*/
|
||||
/** Gets active language code */
|
||||
public static function getCurrentLanguage() {
|
||||
return self::$currentLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|