mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 17:50:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
858f58d19c | ||
|
|
06c8565b94 | ||
|
|
a4bb4dce23 | ||
|
|
66db78c950 | ||
|
|
f0caf2323e | ||
|
|
c5573075b6 | ||
|
|
6313db3f05 | ||
|
|
3ae321fcbd | ||
|
|
a017bf876f | ||
|
|
193345e616 | ||
|
|
3165effa8a | ||
|
|
6efd049851 | ||
|
|
0fdba633f4 | ||
|
|
b2b1e75ec6 | ||
|
|
4df0829c80 | ||
|
|
f53f9c2357 | ||
|
|
3e413966e1 | ||
|
|
8b32500800 | ||
|
|
2535c55670 | ||
|
|
e9635e08d3 | ||
|
|
86a2703dd7 | ||
|
|
883823f5bc | ||
|
|
60e4d6bcea | ||
|
|
4d2ecb63d1 |
No files matched your search
@@ -10,6 +10,8 @@ jobs:
|
||||
build:
|
||||
name: Build for ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
@@ -38,6 +40,12 @@ jobs:
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
||||
- name: Download Tor Binaries
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x scripts/download_tor.sh
|
||||
./scripts/download_tor.sh
|
||||
|
||||
- name: Build with PyInstaller
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -51,8 +59,35 @@ jobs:
|
||||
--name "${{ matrix.artifact_name }}" \
|
||||
voxvera/cli.py
|
||||
|
||||
- name: Build .deb (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
chmod +x packaging/build_deb.sh
|
||||
./packaging/build_deb.sh dist/${{ matrix.artifact_name }}
|
||||
|
||||
- name: Build .dmg (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
chmod +x packaging/build_dmg.sh
|
||||
./packaging/build_dmg.sh dist/${{ matrix.artifact_name }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.asset_name }}
|
||||
path: dist/${{ matrix.artifact_name }}
|
||||
path: |
|
||||
dist/${{ matrix.artifact_name }}
|
||||
voxvera/resources/bin/*.deb
|
||||
voxvera/resources/bin/*.dmg
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
dist/${{ matrix.artifact_name }}
|
||||
voxvera/resources/bin/*.deb
|
||||
voxvera/resources/bin/*.dmg
|
||||
fail_on_unmatched_files: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,62 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install build tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build pyinstaller
|
||||
- name: Build wheel
|
||||
run: python -m build --wheel --sdist
|
||||
- name: Build binary
|
||||
run: |
|
||||
pyinstaller --onefile \
|
||||
--add-data "voxvera/locales:voxvera/locales" \
|
||||
--add-data "voxvera/src:voxvera/src" \
|
||||
--add-data "voxvera/templates:voxvera/templates" \
|
||||
--add-data "voxvera/resources:voxvera/resources" \
|
||||
--add-data "requirements.txt:." \
|
||||
--name voxvera \
|
||||
voxvera/cli.py
|
||||
- name: Create AppImage
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y wget
|
||||
bash packaging/build_appimage.sh
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
docker build -t ghcr.io/${OWNER_LC}/voxvera:${{ github.ref_name }} .
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||
docker push ghcr.io/${OWNER_LC}/voxvera:${{ github.ref_name }}
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
dist/*.tar.gz
|
||||
dist/voxvera
|
||||
dist/VoxVera.AppImage
|
||||
@@ -46,3 +46,6 @@ voxvera/src/qrcode-tear-offs.png
|
||||
# PyInstaller
|
||||
*.spec
|
||||
dist_bin/
|
||||
voxvera/resources/bin/voxvera-*
|
||||
site/download/voxvera-portable.zip
|
||||
site/download/voxvera-linux
|
||||
@@ -12,12 +12,12 @@ VoxVera is designed for maximum portability and censorship resistance.
|
||||
|
||||
### 1. Standalone Binaries (Recommended)
|
||||
Download the zero-dependency executable for your OS:
|
||||
- 🐧 [**Linux**](voxvera/resources/bin/voxvera-linux)
|
||||
- 🪟 **Windows** (Coming soon - use Portable ZIP)
|
||||
- 🍏 **macOS** (Coming soon - use Portable ZIP)
|
||||
- 🐧 [**Linux**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-linux)
|
||||
- 🪟 [**Windows**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-windows.exe)
|
||||
- 🍏 [**macOS**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-macos)
|
||||
|
||||
### 2. Universal Mirroring (The "Viral" Method)
|
||||
If you found a physical VoxVera flyer, scan it and click **"Download Tool & Source"**. You will receive a `voxvera-portable.zip` containing the full tool, all dependencies, and all 14 languages—ready to run offline.
|
||||
If you found a physical VoxVera flyer, scan it and click **"Download"**. You will receive a `voxvera-portable.zip` containing the full tool, all dependencies, and all 14 languages—ready to run offline.
|
||||
|
||||
### 3. One-Line Installer (Linux/macOS)
|
||||
```bash
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
# VoxVera Release Process
|
||||
|
||||
**⚠️ ATTENTION AI AGENTS (CRITICAL WORKFLOW):**
|
||||
Do **NOT** automatically package, tag, or push a new release after completing a set of changes. Releases should **ONLY** be triggered when the user explicitly requests one (e.g., "Package a release now" or "Let's release this").
|
||||
Instead of releasing immediately, you must append a summary of your completed work to the `UPCOMING_RELEASE.md` file. This allows development to span multiple context sessions and ensures that new releases contain a substantial amount of value. When a release is finally requested, use the contents of `UPCOMING_RELEASE.md` to format the GitHub Release notes, and then clear the document for the next cycle.
|
||||
|
||||
This document outlines the mandatory steps to perform before every release to ensure code quality, cross-language integrity, and professional distribution assets.
|
||||
|
||||
## 1. Pre-Flight Checks (Quality Assurance)
|
||||
|
||||
Before any release, the codebase must pass all internal quality checks **without taking any shortcuts**. All tests must be rigorous, use real data where appropriate, and passing them should be a true mark of quality.
|
||||
|
||||
### Run Tests
|
||||
Ensure all core CLI, E2E, and Integrity tests pass:
|
||||
```bash
|
||||
pytest tests/
|
||||
```
|
||||
*Note: The `test_integrity.py` check is critical as it verifies that all 14+ language locales have synchronized keys and all binary bundles are 100% self-contained.*
|
||||
|
||||
### Run Linting
|
||||
Ensure the code adheres to style guidelines (no trailing whitespace, proper spacing, etc.):
|
||||
```bash
|
||||
# Using flake8 (excluding vendored dependencies)
|
||||
flake8 voxvera/ --exclude=voxvera/vendor/
|
||||
```
|
||||
|
||||
## 2. Update & Generate Localized Assets
|
||||
|
||||
Before releasing, you must ensure that **all documentation accurately reflects the new features and functions** introduced in the release.
|
||||
|
||||
All documentation and site templates must be synchronized with the translation system. This ensures that any changes to documentation are reflected across all 14+ supported languages.
|
||||
|
||||
### Synchronize Documentation
|
||||
If you have modified any documentation templates in `docs/templates/` (which you should, to cover new features), you **must** use the translation system to regenerate the localized documentation before release:
|
||||
```bash
|
||||
# Regenerate docs/ folders for all languages from templates and locales
|
||||
python3 -m voxvera.cli build-docs
|
||||
```
|
||||
|
||||
### Synchronize Site Assets
|
||||
The main `site/` directory (including QRs, HTML, and portable Zips) must also be refreshed to include the latest binary names and locale strings:
|
||||
```bash
|
||||
# Refresh site/ directory
|
||||
python3 -m voxvera.cli build-site
|
||||
```
|
||||
|
||||
## 3. Build Distribution Binaries
|
||||
|
||||
Standardized binaries and AppImages must be generated for the current platform.
|
||||
|
||||
### Standard Binary
|
||||
Generates an architecture-specific binary (e.g., `voxvera-linux-x86_64`) in `voxvera/resources/bin/`.
|
||||
```bash
|
||||
./scripts/build_binaries.sh
|
||||
```
|
||||
|
||||
### AppImage (Linux)
|
||||
Wraps the standard binary into a self-contained AppImage.
|
||||
```bash
|
||||
./packaging/build_appimage.sh
|
||||
```
|
||||
|
||||
### Flatpak (Linux)
|
||||
Generates a standalone `.flatpak` bundle. Requires `flatpak-builder`.
|
||||
```bash
|
||||
./packaging/build_flatpak.sh
|
||||
```
|
||||
|
||||
### Debian Package (Linux)
|
||||
Generates a `.deb` package for Debian/Ubuntu systems.
|
||||
```bash
|
||||
./packaging/build_deb.sh
|
||||
```
|
||||
|
||||
### DMG (macOS)
|
||||
Generates a `.dmg` installer for macOS (must be run on macOS).
|
||||
```bash
|
||||
./packaging/build_dmg.sh
|
||||
```
|
||||
|
||||
## 4. Release Execution
|
||||
|
||||
1. **Version Bump:** Update `__version__` in `voxvera/__init__.py` and `version` in `pyproject.toml`.
|
||||
2. **Commit & Tag:**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "chore: release vX.Y.Z"
|
||||
git tag -a vX.Y.Z -m "VoxVera vX.Y.Z"
|
||||
git push origin main --tags
|
||||
```
|
||||
## 5. Automated Releases (GitHub Actions)
|
||||
|
||||
The repository is configured with a **Build Standalone Binaries** workflow that automatically runs when a new tag (`v*`) is pushed. This workflow:
|
||||
- Builds native executables for **Linux**, **Windows (.exe)**, and **macOS**.
|
||||
- Automatically attaches these binaries to the corresponding GitHub Release.
|
||||
|
||||
This ensures that Windows users receive a professional `voxvera-windows.exe` rather than having to rely on `.bat` or `.sh` scripts.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Upcoming Release Notes
|
||||
|
||||
*This document serves as a running changelog for the NEXT version of VoxVera. AI agents must append notable changes here as they are completed. Once a release is explicitly requested by the user, these notes will be used to format the GitHub Release description, and this file should be cleared/reset for the next cycle.*
|
||||
|
||||
### Version: Unreleased
|
||||
|
||||
#### 🚀 New Features
|
||||
- *(Add new features here)*
|
||||
|
||||
#### 🛠 Improvements
|
||||
- *(Add improvements here)*
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
- *(Add bug fixes here)*
|
||||
+28
-20
@@ -4,31 +4,39 @@ This guide covers common CLI workflows. See `docs/docker.md` for Docker instruct
|
||||
|
||||
## Prerequisites
|
||||
|
||||
VoxVera is designed to be highly portable and requires minimal system dependencies.
|
||||
VoxVera is designed to be highly portable. You can run it directly from the source code on any platform with Python installed.
|
||||
|
||||
### 1. Standalone Binaries (Recommended)
|
||||
You can download standalone, zero-dependency binaries for your operating system:
|
||||
- **Linux:** `voxvera-linux`
|
||||
- **Windows:** `voxvera-windows.exe`
|
||||
- **macOS:** `voxvera-macos`
|
||||
|
||||
These binaries include everything needed to run VoxVera (except `onionshare-cli`).
|
||||
|
||||
### 2. One-Line Installer
|
||||
Alternatively, install via our automated script:
|
||||
### 1. Automated Install (Recommended)
|
||||
The easiest way to install VoxVera is using our cross-platform installer script:
|
||||
|
||||
**Linux / macOS:**
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
|
||||
```
|
||||
|
||||
### 3. Manual Python Install
|
||||
If you prefer to run from source:
|
||||
|
||||
```bash
|
||||
pipx install 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
|
||||
sudo apt install tor onionshare-cli # Debian/Ubuntu
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
irm https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.ps1 | iex
|
||||
```
|
||||
|
||||
### 2. Manual Source Install
|
||||
If you have downloaded the source code ZIP:
|
||||
|
||||
1. **Extract** the ZIP file.
|
||||
2. **Open a terminal** in the extracted folder.
|
||||
3. **Run the setup script**:
|
||||
- **Linux/macOS:** `./setup.sh`
|
||||
- **Windows:** `.\voxvera-install.sh` (or run `python -m pip install .`)
|
||||
|
||||
## Uninstallation
|
||||
|
||||
If you wish to remove VoxVera from your system, you can use the provided uninstall scripts from the source directory. This will safely remove the CLI tool and any downloaded dependencies.
|
||||
|
||||
- **Linux / macOS:** Run `./uninstall.sh`
|
||||
- **Windows (PowerShell):** Run `.\uninstall.ps1`
|
||||
|
||||
*Note: Uninstallation will not delete your generated flyers or your `.onion` keys stored in the `~/voxvera-exports` or local `host/` folders.*
|
||||
|
||||
## Step-by-Step
|
||||
|
||||
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
|
||||
@@ -79,10 +87,10 @@ Features:
|
||||
|
||||
To ensure VoxVera remains accessible even if central repositories are censored, every flyer acts as a mirror for the tool.
|
||||
|
||||
When you host a flyer, the **"Download Tool & Source"** button on the landing page provides a `voxvera-portable.zip` containing:
|
||||
When you host a flyer, the **"Download Source Code"** button on the landing page provides a `voxvera-source.zip` containing:
|
||||
- The full source code and all supported languages.
|
||||
- All Python dependencies (pre-vendored).
|
||||
- Cross-platform Tor binaries.
|
||||
- Installation and setup scripts for all platforms.
|
||||
- Complete documentation and templates.
|
||||
|
||||
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
|
||||
|
||||
|
||||
+28
-20
@@ -4,31 +4,39 @@ This guide covers common CLI workflows. See `docs/docker.md` for Docker instruct
|
||||
|
||||
## Prerequisites
|
||||
|
||||
VoxVera is designed to be highly portable and requires minimal system dependencies.
|
||||
VoxVera is designed to be highly portable. You can run it directly from the source code on any platform with Python installed.
|
||||
|
||||
### 1. Standalone Binaries (Recommended)
|
||||
You can download standalone, zero-dependency binaries for your operating system:
|
||||
- **Linux:** `voxvera-linux`
|
||||
- **Windows:** `voxvera-windows.exe`
|
||||
- **macOS:** `voxvera-macos`
|
||||
|
||||
These binaries include everything needed to run VoxVera (except `onionshare-cli`).
|
||||
|
||||
### 2. One-Line Installer
|
||||
Alternatively, install via our automated script:
|
||||
### 1. Automated Install (Recommended)
|
||||
The easiest way to install VoxVera is using our cross-platform installer script:
|
||||
|
||||
**Linux / macOS:**
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
|
||||
```
|
||||
|
||||
### 3. Manual Python Install
|
||||
If you prefer to run from source:
|
||||
|
||||
```bash
|
||||
pipx install 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
|
||||
sudo apt install tor onionshare-cli # Debian/Ubuntu
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
irm https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.ps1 | iex
|
||||
```
|
||||
|
||||
### 2. Manual Source Install
|
||||
If you have downloaded the source code ZIP:
|
||||
|
||||
1. **Extract** the ZIP file.
|
||||
2. **Open a terminal** in the extracted folder.
|
||||
3. **Run the setup script**:
|
||||
- **Linux/macOS:** `./setup.sh`
|
||||
- **Windows:** `.\voxvera-install.sh` (or run `python -m pip install .`)
|
||||
|
||||
## Uninstallation
|
||||
|
||||
If you wish to remove VoxVera from your system, you can use the provided uninstall scripts from the source directory. This will safely remove the CLI tool and any downloaded dependencies.
|
||||
|
||||
- **Linux / macOS:** Run `./uninstall.sh`
|
||||
- **Windows (PowerShell):** Run `.\uninstall.ps1`
|
||||
|
||||
*Note: Uninstallation will not delete your generated flyers or your `.onion` keys stored in the `~/voxvera-exports` or local `host/` folders.*
|
||||
|
||||
## Step-by-Step
|
||||
|
||||
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
|
||||
@@ -79,10 +87,10 @@ Features:
|
||||
|
||||
To ensure VoxVera remains accessible even if central repositories are censored, every flyer acts as a mirror for the tool.
|
||||
|
||||
When you host a flyer, the **"הורד כלי וקוד מקור"** button on the landing page provides a `voxvera-portable.zip` containing:
|
||||
When you host a flyer, the **"הורד כלי וקוד מקור"** button on the landing page provides a `voxvera-source.zip` containing:
|
||||
- The full source code and all supported languages.
|
||||
- All Python dependencies (pre-vendored).
|
||||
- Cross-platform Tor binaries.
|
||||
- Installation and setup scripts for all platforms.
|
||||
- Complete documentation and templates.
|
||||
|
||||
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
|
||||
|
||||
|
||||
Vendored
+28
-20
@@ -4,31 +4,39 @@ This guide covers common CLI workflows. See `docs/docker.md` for Docker instruct
|
||||
|
||||
## Prerequisites
|
||||
|
||||
VoxVera is designed to be highly portable and requires minimal system dependencies.
|
||||
VoxVera is designed to be highly portable. You can run it directly from the source code on any platform with Python installed.
|
||||
|
||||
### 1. Standalone Binaries (Recommended)
|
||||
You can download standalone, zero-dependency binaries for your operating system:
|
||||
- **Linux:** `voxvera-linux`
|
||||
- **Windows:** `voxvera-windows.exe`
|
||||
- **macOS:** `voxvera-macos`
|
||||
|
||||
These binaries include everything needed to run VoxVera (except `onionshare-cli`).
|
||||
|
||||
### 2. One-Line Installer
|
||||
Alternatively, install via our automated script:
|
||||
### 1. Automated Install (Recommended)
|
||||
The easiest way to install VoxVera is using our cross-platform installer script:
|
||||
|
||||
**Linux / macOS:**
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
|
||||
```
|
||||
|
||||
### 3. Manual Python Install
|
||||
If you prefer to run from source:
|
||||
|
||||
```bash
|
||||
pipx install 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
|
||||
sudo apt install tor onionshare-cli # Debian/Ubuntu
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
irm https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.ps1 | iex
|
||||
```
|
||||
|
||||
### 2. Manual Source Install
|
||||
If you have downloaded the source code ZIP:
|
||||
|
||||
1. **Extract** the ZIP file.
|
||||
2. **Open a terminal** in the extracted folder.
|
||||
3. **Run the setup script**:
|
||||
- **Linux/macOS:** `./setup.sh`
|
||||
- **Windows:** `.\voxvera-install.sh` (or run `python -m pip install .`)
|
||||
|
||||
## Uninstallation
|
||||
|
||||
If you wish to remove VoxVera from your system, you can use the provided uninstall scripts from the source directory. This will safely remove the CLI tool and any downloaded dependencies.
|
||||
|
||||
- **Linux / macOS:** Run `./uninstall.sh`
|
||||
- **Windows (PowerShell):** Run `.\uninstall.ps1`
|
||||
|
||||
*Note: Uninstallation will not delete your generated flyers or your `.onion` keys stored in the `~/voxvera-exports` or local `host/` folders.*
|
||||
|
||||
## Step-by-Step
|
||||
|
||||
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
|
||||
@@ -79,10 +87,10 @@ Features:
|
||||
|
||||
To ensure VoxVera remains accessible even if central repositories are censored, every flyer acts as a mirror for the tool.
|
||||
|
||||
When you host a flyer, the **"{{t('web.download_button')}}"** button on the landing page provides a `voxvera-portable.zip` containing:
|
||||
When you host a flyer, the **"{{t('web.download_button')}}"** button on the landing page provides a `voxvera-source.zip` containing:
|
||||
- The full source code and all supported languages.
|
||||
- All Python dependencies (pre-vendored).
|
||||
- Cross-platform Tor binaries.
|
||||
- Installation and setup scripts for all platforms.
|
||||
- Complete documentation and templates.
|
||||
|
||||
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "Vox Vera Printable Flyers",
|
||||
"folder_name": "voxvera",
|
||||
"title": "TOP SECRET",
|
||||
"subtitle": "DO <span class=\"redacted\">NOT</span> DISTRIBUTE",
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.\n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.\n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser.",
|
||||
|
||||
+22
-2
@@ -128,7 +128,17 @@ if command_exists pipx; then
|
||||
echo "pipx install failed, downloading binary"
|
||||
install_dir="$HOME/.local/bin"
|
||||
mkdir -p "$install_dir"
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera"
|
||||
|
||||
# Detect OS for binary selection
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
case "$OS" in
|
||||
linux*) BINARY="voxvera-linux" ;;
|
||||
darwin*) BINARY="voxvera-macos" ;;
|
||||
msys*|cygwin*|mingw*) BINARY="voxvera-windows.exe" ;;
|
||||
*) BINARY="voxvera-linux" ;; # fallback to linux
|
||||
esac
|
||||
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/${BINARY}"
|
||||
dest="$install_dir/voxvera"
|
||||
if download_binary "$url" "$dest"; then
|
||||
check_local_bin
|
||||
@@ -146,7 +156,17 @@ if command_exists pipx; then
|
||||
else
|
||||
install_dir="$HOME/.local/bin"
|
||||
mkdir -p "$install_dir"
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera"
|
||||
|
||||
# Detect OS for binary selection
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
case "$OS" in
|
||||
linux*) BINARY="voxvera-linux" ;;
|
||||
darwin*) BINARY="voxvera-macos" ;;
|
||||
msys*|cygwin*|mingw*) BINARY="voxvera-windows.exe" ;;
|
||||
*) BINARY="voxvera-linux" ;; # fallback to linux
|
||||
esac
|
||||
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/${BINARY}"
|
||||
dest="$install_dir/voxvera"
|
||||
if download_binary "$url" "$dest"; then
|
||||
check_local_bin
|
||||
|
||||
+25
-13
@@ -1,21 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ ! -f dist/voxvera ]; then
|
||||
echo "Run PyInstaller first" >&2
|
||||
# Ensure we are in project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
ARCH=$(uname -m)
|
||||
BINARY="voxvera/resources/bin/voxvera-linux-$ARCH"
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY. Run scripts/build_binaries.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APPDIR=dist/AppDir
|
||||
APPDIR=build/AppDir
|
||||
mkdir -p "$APPDIR/usr/bin"
|
||||
cp dist/voxvera "$APPDIR/usr/bin/voxvera"
|
||||
cp "$BINARY" "$APPDIR/usr/bin/voxvera"
|
||||
chmod +x "$APPDIR/usr/bin/voxvera"
|
||||
mkdir -p "$APPDIR/usr/lib/voxvera/resources"
|
||||
cp -r voxvera/resources/tor "$APPDIR/usr/lib/voxvera/resources/"
|
||||
# also bundle Tor for the Electron GUI
|
||||
mkdir -p gui/electron/voxvera/resources
|
||||
cp -r voxvera/resources/tor gui/electron/voxvera/resources/
|
||||
|
||||
# Desktop file
|
||||
cat > "$APPDIR/voxvera.desktop" <<EOD
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
@@ -23,13 +25,23 @@ Name=VoxVera
|
||||
Exec=voxvera
|
||||
Icon=voxvera
|
||||
Categories=Utility;
|
||||
Terminal=true
|
||||
EOD
|
||||
|
||||
touch "$APPDIR/voxvera.png"
|
||||
# Use a generic icon since we don't have one
|
||||
# (AppImageTool requires an icon file to exist)
|
||||
# We will copy a qrcode as a placeholder if nothing else exists
|
||||
cp site/qrcode-content.png "$APPDIR/voxvera.png"
|
||||
|
||||
wget -q https://github.com/AppImage/AppImageKit/releases/latest/download/appimagetool-x86_64.AppImage -O appimagetool
|
||||
# Download appimagetool
|
||||
echo "Downloading appimagetool..."
|
||||
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O appimagetool
|
||||
chmod +x appimagetool
|
||||
./appimagetool --appimage-extract-and-run "$APPDIR" dist/VoxVera.AppImage
|
||||
|
||||
# Run appimagetool
|
||||
echo "Running appimagetool..."
|
||||
export ARCH
|
||||
./appimagetool --appimage-extract-and-run "$APPDIR" voxvera/resources/bin/VoxVera-x86_64.AppImage
|
||||
rm appimagetool
|
||||
|
||||
echo "AppImage created at dist/VoxVera.AppImage"
|
||||
echo "AppImage created at voxvera/resources/bin/VoxVera-x86_64.AppImage"
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Ensure we are in project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
ARCH=$(uname -m)
|
||||
# Debian uses amd64 instead of x86_64
|
||||
if [ "$ARCH" == "x86_64" ]; then
|
||||
DEB_ARCH="amd64"
|
||||
elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then
|
||||
DEB_ARCH="arm64"
|
||||
else
|
||||
DEB_ARCH="$ARCH"
|
||||
fi
|
||||
BINARY=${1:-"voxvera/resources/bin/voxvera-linux-$ARCH"}
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
VERSION=$(grep "__version__" voxvera/__init__.py | cut -d '"' -f 2)
|
||||
PKG_DIR="build/voxvera_${VERSION}_${DEB_ARCH}"
|
||||
|
||||
echo "Building .deb package for version $VERSION ($DEB_ARCH)..."
|
||||
|
||||
rm -rf "$PKG_DIR"
|
||||
mkdir -p "$PKG_DIR/usr/bin"
|
||||
mkdir -p "$PKG_DIR/DEBIAN"
|
||||
|
||||
cp "$BINARY" "$PKG_DIR/usr/bin/voxvera"
|
||||
chmod +x "$PKG_DIR/usr/bin/voxvera"
|
||||
|
||||
# Copy control file and update version/arch
|
||||
cp packaging/deb/DEBIAN/control "$PKG_DIR/DEBIAN/control"
|
||||
sed -i "s/^Version: .*/Version: $VERSION/" "$PKG_DIR/DEBIAN/control"
|
||||
sed -i "s/^Architecture: .*/Architecture: $DEB_ARCH/" "$PKG_DIR/DEBIAN/control"
|
||||
|
||||
# Build the package
|
||||
dpkg-deb --build "$PKG_DIR" "voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
|
||||
|
||||
echo "Debian package created at voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Ensure we are in project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# This script is intended to be run on macOS
|
||||
if [ "$(uname)" != "Darwin" ]; then
|
||||
echo "Error: .dmg creation requires macOS." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARCH=$(uname -m)
|
||||
BINARY=${1:-"voxvera/resources/bin/voxvera-macos-$ARCH"}
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$(grep "__version__" voxvera/__init__.py | cut -d '"' -f 2)
|
||||
DMG_NAME="VoxVera_${VERSION}_${ARCH}.dmg"
|
||||
STAGING_DIR="build/dmg_staging"
|
||||
|
||||
echo "Building .dmg package for version $VERSION ($ARCH)..."
|
||||
|
||||
rm -rf "$STAGING_DIR"
|
||||
mkdir -p "$STAGING_DIR"
|
||||
|
||||
# For a CLI tool, we just put the binary in the DMG.
|
||||
# In the future, we could create an .app bundle if there's a GUI.
|
||||
cp "$BINARY" "$STAGING_DIR/voxvera"
|
||||
chmod +x "$STAGING_DIR/voxvera"
|
||||
|
||||
# Create the DMG
|
||||
hdiutil create -volname "VoxVera $VERSION" -srcfolder "$STAGING_DIR" -ov -format UDZO "voxvera/resources/bin/$DMG_NAME"
|
||||
|
||||
echo "DMG created at voxvera/resources/bin/$DMG_NAME"
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Ensure we are in project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
ARCH=$(uname -m)
|
||||
BINARY="voxvera/resources/bin/voxvera-linux-$ARCH"
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY. Run scripts/build_binaries.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v flatpak-builder &> /dev/null; then
|
||||
echo "flatpak-builder not found. Please install it (e.g., sudo apt install flatpak-builder)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building Flatpak package..."
|
||||
|
||||
BUILD_DIR=build/flatpak-build
|
||||
REPO_DIR=build/flatpak-repo
|
||||
MANIFEST=packaging/flatpak/org.voxvera.VoxVera.yml
|
||||
|
||||
# Clean previous
|
||||
rm -rf "$BUILD_DIR" "$REPO_DIR"
|
||||
|
||||
# Build
|
||||
flatpak-builder --force-clean --repo="$REPO_DIR" "$BUILD_DIR" "$MANIFEST"
|
||||
|
||||
# Generate a standalone .flatpak file
|
||||
flatpak build-bundle "$REPO_DIR" voxvera/resources/bin/VoxVera-x86_64.flatpak org.voxvera.VoxVera
|
||||
|
||||
echo "Flatpak bundle created at voxvera/resources/bin/VoxVera-x86_64.flatpak"
|
||||
@@ -0,0 +1,9 @@
|
||||
Package: voxvera
|
||||
Version: 0.1.4
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Maintainer: VoxVera <[email protected]>
|
||||
Description: VoxVera CLI utilities
|
||||
Anonymous message-spreading and flyer-generation tool.
|
||||
Depends: libc6
|
||||
@@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=VoxVera
|
||||
Comment=Anonymous message-spreading and flyer-generation tool
|
||||
Exec=voxvera
|
||||
Icon=org.voxvera.VoxVera
|
||||
Categories=Utility;Network;
|
||||
Terminal=true
|
||||
StartupWMClass=voxvera
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 436 B |
@@ -0,0 +1,27 @@
|
||||
app-id: org.voxvera.VoxVera
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: '23.08'
|
||||
sdk: org.freedesktop.Sdk
|
||||
command: voxvera
|
||||
finish-args:
|
||||
- --share=network
|
||||
- --socket=fallback-x11
|
||||
- --socket=wayland
|
||||
- --device=dri
|
||||
- --share=ipc
|
||||
- --filesystem=xdg-run/tor:ro # If using external Tor
|
||||
modules:
|
||||
- name: voxvera
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- install -D voxvera /app/bin/voxvera
|
||||
- install -D org.voxvera.VoxVera.desktop /app/share/applications/org.voxvera.VoxVera.desktop
|
||||
- install -D org.voxvera.VoxVera.png /app/share/icons/hicolor/512x512/apps/org.voxvera.VoxVera.png
|
||||
sources:
|
||||
- type: file
|
||||
path: ../../voxvera/resources/bin/voxvera-linux-x86_64
|
||||
dest-filename: voxvera
|
||||
- type: file
|
||||
path: org.voxvera.VoxVera.desktop
|
||||
- type: file
|
||||
path: org.voxvera.VoxVera.png
|
||||
+3
-2
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
|
||||
|
||||
[project]
|
||||
name = "voxvera"
|
||||
version = "0.1.1"
|
||||
version = "0.1.7"
|
||||
description = "VoxVera CLI utilities"
|
||||
readme = "README.md"
|
||||
authors = [{name = "VoxVera"}]
|
||||
@@ -26,9 +26,10 @@ dependencies = [
|
||||
"Pillow",
|
||||
"jsmin",
|
||||
"htmlmin2",
|
||||
"pypdf",
|
||||
"wcwidth",
|
||||
"onionshare-cli",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
"pytest",
|
||||
|
||||
@@ -6,3 +6,5 @@ jsmin
|
||||
htmlmin2
|
||||
pypdf
|
||||
wcwidth
|
||||
onionshare-cli
|
||||
|
||||
@@ -19,17 +19,24 @@ mkdir -p voxvera/resources/bin
|
||||
|
||||
echo "Building VoxVera binary for $(uname)..."
|
||||
|
||||
echo "Ensuring Tor binaries are downloaded..."
|
||||
bash scripts/download_tor.sh
|
||||
|
||||
# Use python module to call pyinstaller
|
||||
PY_CMD="python3 -m PyInstaller"
|
||||
|
||||
# Build the binary
|
||||
ARCH=$(uname -m)
|
||||
OS_NAME=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
BINARY_NAME="voxvera-$OS_NAME-$ARCH"
|
||||
|
||||
$PY_CMD --onefile \
|
||||
--add-data "voxvera/locales:voxvera/locales" \
|
||||
--add-data "voxvera/src:voxvera/src" \
|
||||
--add-data "voxvera/templates:voxvera/templates" \
|
||||
--add-data "voxvera/resources:voxvera/resources" \
|
||||
--add-data "requirements.txt:." \
|
||||
--name "voxvera-$(uname -s | tr '[:upper:]' '[:lower:]')" \
|
||||
--name "$BINARY_NAME" \
|
||||
--distpath voxvera/resources/bin \
|
||||
voxvera/cli.py
|
||||
|
||||
|
||||
+13
-6
@@ -1,12 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${VERSION:-13.5.18}"
|
||||
BASE_URL="https://www.torproject.org/dist/torbrowser"
|
||||
VERSION="${VERSION:-15.0.7}"
|
||||
BASE_URL="https://dist.torproject.org/torbrowser"
|
||||
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then
|
||||
TOR_ARCH="aarch64"
|
||||
else
|
||||
TOR_ARCH="x86_64"
|
||||
fi
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux*) PLATFORM=linux; ARCHIVE="tor-expert-bundle-linux-x86_64-${VERSION}.tar.gz"; EXE=tor;;
|
||||
Darwin*) PLATFORM=mac; ARCHIVE="tor-expert-bundle-macos-x86_64-${VERSION}.tar.gz"; EXE=tor;;
|
||||
Linux*) PLATFORM=linux; ARCHIVE="tor-expert-bundle-linux-${TOR_ARCH}-${VERSION}.tar.gz"; EXE=tor;;
|
||||
Darwin*) PLATFORM=mac; ARCHIVE="tor-expert-bundle-macos-${TOR_ARCH}-${VERSION}.tar.gz"; EXE=tor;;
|
||||
MINGW*|MSYS*|CYGWIN*) PLATFORM=win; ARCHIVE="tor-expert-bundle-windows-x86_64-${VERSION}.tar.gz"; EXE=tor.exe;;
|
||||
*) echo "Unsupported OS" >&2; exit 1;;
|
||||
esac
|
||||
@@ -26,10 +33,10 @@ DEST="$(dirname "$0")/../voxvera/resources/tor/$PLATFORM"
|
||||
mkdir -p "$DEST"
|
||||
|
||||
TOR_BIN=$(find "$TMPDIR" -type f -name "$EXE" | head -n 1)
|
||||
OBFS_BIN=$(find "$TMPDIR" -type f -name "obfs4proxy*" | head -n 1)
|
||||
OBFS_BIN=$(find "$TMPDIR" -type f \( -name "lyrebird*" -o -name "obfs4proxy*" \) | head -n 1)
|
||||
|
||||
if [[ -z "$TOR_BIN" || -z "$OBFS_BIN" ]]; then
|
||||
echo "Failed to locate tor or obfs4proxy in archive" >&2
|
||||
echo "Failed to locate tor or lyrebird/obfs4proxy in archive" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "Vox Vera Printable Flyers",
|
||||
"folder_name": "voxvera",
|
||||
"title": "TOP SECRET",
|
||||
"subtitle": "DO <span class=\"redacted\">NOT</span> DISTRIBUTE",
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n",
|
||||
"url_message": "Get VoxVera and start spreading your message.",
|
||||
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
+53
-5
@@ -330,10 +330,55 @@
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
background-color: #f9f9f9;
|
||||
min-width: 200px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
border: 1px solid #000;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dropdown-content a:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown:hover .download-button {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
// VoxVera Multi-Language Engine
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.\n\n\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.\n\n\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.\n\n\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\u0631 \u0631\u0633\u0627\u0644\u062a\u0643."}}, "de": {"meta": {"language_name": "Deutsch", "flag": "\ud83c\udde9\ud83c\uddea"}, "web": {"tear_off_tor_browser": "Tor-Browser verLine truncated
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f"}, "landing": {"title": "TOP SECRET", "subtitle": "DO ~~NOT~~ DISTRIBUTE", "headline": "OPERATION VOX VERA", "content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n", "url_message": "Get VoxVera and start spreading your message.", "name": "Vox Vera Printable Flyers"}}, "de": {"meta": {"language_name": "Deutsch", "flag": "\ud83c\udde9\ud83c\uddea"}, "web": {"tear_off_tor_browser": "Tor-Browser verwenden", "tear_off_desc": "um diese Nachricht zu verbreiten, gehen Sie zu", "tear_off_footer": "Klicken Sie auf die Schaltfl\u00e4che unten zum Drucken", "built_with": "erstellt mit", "print_button": "Flyer drucken", "download_button": "Tool & Quellcode herunterladen"}, "landing": {"title": "TOP SECRET", "subtitle": "DO ~~NOT~~ DISTRIBUTE", "headline": "OPERATION VOX VERA", "content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n", "url_message": "Get VoxVera and start spreading your message.", "name": "Vox Vera Printable Flyers"}}, "en": {"meta": {"language_name": "English", "flag": "\ud83c\uddfa\ud83c\uddf8"}, "web": {"tear_off_tor_browser": "Use Tor Browser", "tear_off_desc": "to help spread this message go to", "tear_off_footer": "click the button below to print", "built_with": "built with", "print_button": "Print this page", "download_button": "Download Source Code"}, "landing": {"title": "TOP SECRET", "subtitle": "DO ~~NOT~~ DISTRIBUTE", "headline": "OPERATION VOX VERA", "content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n", "url_message": "Get VoxVera and start spreading your message.", "name": "Vox Vera Printable Flyers"}}, "es": {"meta": {"language_name": "Espa\u00f1ol", "flag": "\ud83c\uddea\ud83c\uddf8"}, "web": {"tear_off_tor_browser": "Use el Navegador Tor",Line truncated
|
||||
|
||||
function t(path, lang) {
|
||||
const parts = path.split('.');
|
||||
@@ -512,13 +557,14 @@
|
||||
<h1 id="operation-title-2" data-i18n="landing.headline">OPERATION VOX VERA</h1>
|
||||
<hr>
|
||||
<div class="message">
|
||||
<div class="text-container" id="main-content" contenteditable="false" data-i18n="landing.content">Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.
|
||||
<div class="text-container" id="main-content" contenteditable="false" data-i18n="landing.content">Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.
|
||||
|
||||
Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.
|
||||
Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.
|
||||
|
||||
Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.
|
||||
|
||||
Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.</div>
|
||||
Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.
|
||||
</div>
|
||||
<div class="qr-code-body">
|
||||
|
||||
<div class="qr-code-url">
|
||||
@@ -537,7 +583,9 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="print-button" onclick="window.print()" data-i18n="web.print_button">Print this page</button>
|
||||
<a class="download-button" href="download/voxvera-portable.zip" download data-i18n="web.download_button">Download Tool & Source</a>
|
||||
<a id="download-link" href="download/voxvera-source.zip" download>
|
||||
<button class="download-button" data-i18n="web.download_button">Download Source Code</button>
|
||||
</a>
|
||||
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
+8
-3
@@ -112,10 +112,15 @@ def test_check_all_present(capsys, monkeypatch):
|
||||
|
||||
|
||||
def test_check_missing(capsys, monkeypatch):
|
||||
def fake_which(cmd):
|
||||
return None if cmd == "onionshare-cli" else "/usr/bin/" + cmd
|
||||
import builtins
|
||||
original_import = builtins.__import__
|
||||
|
||||
monkeypatch.setattr(shutil, "which", fake_which)
|
||||
def fake_import(name, *args, **kwargs):
|
||||
if name == "onionshare_cli":
|
||||
raise ImportError("Mocked missing")
|
||||
return original_import(name, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(builtins, "__import__", fake_import)
|
||||
cli.main(["check"])
|
||||
captured = capsys.readouterr()
|
||||
assert "onionshare-cli" in captured.out
|
||||
|
||||
+9
-3
@@ -46,7 +46,11 @@ def check_tor_running():
|
||||
|
||||
def check_onionshare_cli():
|
||||
"""Check if onionshare-cli is installed."""
|
||||
return shutil.which("onionshare-cli") is not None
|
||||
try:
|
||||
import onionshare_cli # noqa: F401
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@@ -222,12 +226,14 @@ class TestFullWorkflow:
|
||||
host_dir = tmp_path / "host" / "reachtest"
|
||||
assert host_dir.is_dir(), f"Build did not create {host_dir}"
|
||||
|
||||
# Launch onionshare-cli directly against the built host directory
|
||||
# Launch onionshare-cli directly against the built host directory using our internal wrapper
|
||||
logfile = host_dir / "onionshare.log"
|
||||
log_fh = open(logfile, "w")
|
||||
|
||||
cmd = [
|
||||
"onionshare-cli",
|
||||
sys.executable,
|
||||
"-m", "voxvera.cli",
|
||||
"_internal_onionshare",
|
||||
"--website",
|
||||
"--public",
|
||||
"--persistent",
|
||||
|
||||
@@ -115,7 +115,11 @@ def test_portable_bundle_integrity(tmp_path, monkeypatch):
|
||||
shutil.copytree(REPO_ROOT / "voxvera" / "locales", voxvera_dir / "locales")
|
||||
os.makedirs(voxvera_dir / "vendor", exist_ok=True)
|
||||
(voxvera_dir / "vendor" / "anchor.txt").write_text("anchor")
|
||||
(project_root / "voxvera-run.sh").write_text("echo run")
|
||||
|
||||
# Copy real scripts from repo
|
||||
for script in ["voxvera-run.sh", "uninstall.sh", "uninstall.ps1", "install.sh", "install.ps1", "setup.sh"]:
|
||||
if (REPO_ROOT / script).exists():
|
||||
shutil.copy(REPO_ROOT / script, project_root / script)
|
||||
|
||||
# ROOT in cli.py points to the voxvera/ directory
|
||||
monkeypatch.setattr(cli, "ROOT", voxvera_dir)
|
||||
@@ -133,7 +137,9 @@ def test_portable_bundle_integrity(tmp_path, monkeypatch):
|
||||
assert any("voxvera/locales/en.json" in n for n in names)
|
||||
# Verify vendored libs
|
||||
assert any("voxvera/vendor/anchor.txt" in n for n in names)
|
||||
# Verify launchers
|
||||
assert "voxvera.bat" in names
|
||||
# Verify uninstallers
|
||||
assert "uninstall.sh" in names
|
||||
assert "uninstall.ps1" in names
|
||||
|
||||
# Verify root scripts
|
||||
assert "voxvera-run.sh" in names
|
||||
@@ -0,0 +1,22 @@
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
|
||||
Write-Host "Uninstalling VoxVera..."
|
||||
|
||||
# 1. Uninstall via pipx
|
||||
if (Get-Command pipx -ErrorAction SilentlyContinue) {
|
||||
pipx uninstall voxvera | Out-Null
|
||||
}
|
||||
|
||||
# 2. Uninstall via pip
|
||||
if (Get-Command pip -ErrorAction SilentlyContinue) {
|
||||
pip uninstall -y voxvera | Out-Null
|
||||
}
|
||||
|
||||
# 3. Remove manual binary
|
||||
$home = [Environment]::GetFolderPath('UserProfile')
|
||||
$exePath = Join-Path $home '.local\bin\voxvera.exe'
|
||||
if (Test-Path $exePath) {
|
||||
Remove-Item $exePath -Force
|
||||
}
|
||||
|
||||
Write-Host "`nVoxVera has been uninstalled."
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "Uninstalling VoxVera..."
|
||||
|
||||
# 1. Uninstall via pipx if present
|
||||
if command -v pipx >/dev/null 2>&1; then
|
||||
if pipx list | grep -q "package voxvera"; then
|
||||
echo "Removing voxvera via pipx..."
|
||||
pipx uninstall voxvera
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. Uninstall via pip if present
|
||||
if command -v pip >/dev/null 2>&1; then
|
||||
echo "Attempting to remove voxvera via pip..."
|
||||
pip uninstall -y voxvera >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# 3. Remove manual binary installation
|
||||
BINARY_PATH="$HOME/.local/bin/voxvera"
|
||||
if [ -f "$BINARY_PATH" ]; then
|
||||
echo "Removing manual binary at $BINARY_PATH..."
|
||||
rm "$BINARY_PATH"
|
||||
fi
|
||||
|
||||
# 4. Remove configuration and host data (Optional - ask user)
|
||||
# We will leave the host data by default to prevent data loss,
|
||||
# but we can remove the internal src config.
|
||||
# ROOT_DIR="$(dirname "$(readlink -f "$0")")" # This might not be reliable if run via curl
|
||||
|
||||
echo ""
|
||||
echo "VoxVera has been uninstalled."
|
||||
echo "Note: Your generated sites in ~/voxvera-exports or within the source folder were not removed."
|
||||
+1
-1
@@ -46,7 +46,7 @@ install_voxvera() {
|
||||
url=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
|
||||
https://github.com/PR0M3TH3AN/VoxVera/releases/latest)
|
||||
latest="${url%/}" ; latest="${latest##*/}" # vX.Y.Z tag
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/download/${latest/\/}/voxvera"
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/download/${latest/\/}/voxvera-linux"
|
||||
if curl -fsSL "$url" -o "$dest"; then
|
||||
chmod +x "$dest"
|
||||
PATH="$install_dir:$PATH"
|
||||
|
||||
+6
-3
@@ -1,9 +1,12 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.1.1"
|
||||
__version__ = "0.1.7"
|
||||
|
||||
# Add the vendor directory to sys.path so we can run without pip install
|
||||
# Add the vendor directory to sys.path as a fallback.
|
||||
# For standard installs (pip/pipx), the installed dependencies in the venv
|
||||
# should take priority. We append here so vendor acts as a fallback.
|
||||
vendor_dir = Path(__file__).parent / "vendor"
|
||||
if vendor_dir.exists():
|
||||
sys.path.insert(0, str(vendor_dir))
|
||||
if str(vendor_dir) not in sys.path:
|
||||
sys.path.append(str(vendor_dir))
|
||||
+269
-121
@@ -64,6 +64,14 @@ def t(path: str, **kwargs) -> str:
|
||||
|
||||
|
||||
def require_cmd(cmd: str):
|
||||
if cmd == "onionshare-cli":
|
||||
try:
|
||||
import onionshare_cli # noqa: F401
|
||||
return True
|
||||
except ImportError:
|
||||
print("Required tool 'onionshare-cli' not bundled or installed.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
if shutil.which(cmd) is None:
|
||||
print(
|
||||
f"Required command '{cmd}' not found. Please install it.", file=sys.stderr
|
||||
@@ -75,10 +83,11 @@ def require_cmd(cmd: str):
|
||||
def check_deps():
|
||||
console = Console()
|
||||
|
||||
# External CLI tools still required at runtime
|
||||
cli_tools = ["onionshare-cli"]
|
||||
# External CLI tools still required at runtime (none anymore if bundled)
|
||||
cli_tools = []
|
||||
# Python packages used by the build pipeline
|
||||
py_packages = {
|
||||
"onionshare-cli": "onionshare_cli",
|
||||
"qrcode": "qrcode",
|
||||
"PIL (Pillow)": "PIL",
|
||||
"jsmin": "jsmin",
|
||||
@@ -130,6 +139,17 @@ def _open_editor_terminal(initial: str) -> str:
|
||||
import tempfile
|
||||
|
||||
editor = os.environ.get("EDITOR", "nano")
|
||||
if not shutil.which(editor):
|
||||
editor = "nano"
|
||||
if not shutil.which("nano"):
|
||||
# Extreme fallback
|
||||
print("\n--- EDIT CONTENT BELOW (End with Ctrl+D) ---")
|
||||
print("(This is a fallback because neither $EDITOR nor nano were found)")
|
||||
print("--- Current content ---")
|
||||
print(initial or "")
|
||||
print("---")
|
||||
return sys.stdin.read()
|
||||
|
||||
fd, path = tempfile.mkstemp(suffix=".txt")
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
||||
@@ -137,23 +157,32 @@ def _open_editor_terminal(initial: str) -> str:
|
||||
subprocess.call([editor, path])
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
return fh.read()
|
||||
except Exception as e:
|
||||
print(f"Error opening terminal editor: {e}")
|
||||
return initial
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
# 🔧 merged conflicting changes from codex/populate-gui-text-fields-for-editing vs main
|
||||
def open_editor(initial: str) -> str:
|
||||
"""Open a simple GUI text editor with pre-populated content if possible.
|
||||
|
||||
Existing text is pre-filled in the editor. If tkinter or a display
|
||||
server is unavailable, falls back to the user's $EDITOR in the terminal.
|
||||
"""
|
||||
# If no display found (on Linux/Unix), immediately use terminal
|
||||
if os.name != "nt" and not os.environ.get("DISPLAY") and not os.environ.get("WAYLAND_DISPLAY"):
|
||||
return _open_editor_terminal(initial)
|
||||
|
||||
try:
|
||||
import tkinter as tk
|
||||
from tkinter import scrolledtext
|
||||
|
||||
root = tk.Tk()
|
||||
root.title("Edit text")
|
||||
# Bring to front
|
||||
root.attributes("-topmost", True)
|
||||
root.after_idle(root.attributes, "-topmost", False)
|
||||
except Exception:
|
||||
return _open_editor_terminal(initial)
|
||||
|
||||
@@ -168,10 +197,21 @@ def open_editor(initial: str) -> str:
|
||||
result["text"] = text.get("1.0", "end-1c")
|
||||
root.destroy()
|
||||
|
||||
save_btn = tk.Button(root, text="Save", command=save_and_close)
|
||||
save_btn = tk.Button(root, text="Save (or close window)", command=save_and_close)
|
||||
save_btn.pack()
|
||||
root.protocol("WM_DELETE_WINDOW", save_and_close)
|
||||
root.mainloop()
|
||||
|
||||
try:
|
||||
root.mainloop()
|
||||
except KeyboardInterrupt:
|
||||
# If user hits Ctrl+C in terminal while GUI is open, fallback to terminal editor
|
||||
try:
|
||||
root.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
print("\nGUI editor interrupted. Falling back to terminal editor...")
|
||||
return _open_editor_terminal(initial)
|
||||
|
||||
return result["text"]
|
||||
|
||||
|
||||
@@ -217,13 +257,42 @@ def interactive_update(config_path: str):
|
||||
data = load_config(config_path)
|
||||
console = Console()
|
||||
|
||||
# Define the fields and their localized fallback paths
|
||||
# We use these if the value in data is missing or matches the hardcoded English default
|
||||
localized_defaults = {
|
||||
"name": t("landing.name"),
|
||||
"title": t("landing.title"),
|
||||
"subtitle": t("landing.subtitle"),
|
||||
"headline": t("landing.headline"),
|
||||
"content": t("landing.content"),
|
||||
"url_message": t("landing.url_message"),
|
||||
}
|
||||
|
||||
# Hardcoded English defaults from the template for comparison
|
||||
english_defaults = {
|
||||
"name": "Vox Vera Printable Flyers",
|
||||
"title": "TOP SECRET",
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
}
|
||||
|
||||
def get_default(key):
|
||||
val = data.get(key, "")
|
||||
# If empty OR it matches the hardcoded English default, and we have a localized version
|
||||
if (not val or val == english_defaults.get(key)) and localized_defaults.get(key):
|
||||
# Only use localized if it is actually different from the path (token not found)
|
||||
loc_val = localized_defaults.get(key)
|
||||
if loc_val != f"landing.{key}":
|
||||
return loc_val
|
||||
return val
|
||||
|
||||
console.rule(t("cli.init_name"))
|
||||
meta_qs = [
|
||||
{
|
||||
"type": "input",
|
||||
"message": t("cli.init_name"),
|
||||
"name": "name",
|
||||
"default": data.get("name", ""),
|
||||
"default": get_default("name"),
|
||||
"transformer": _len_transform(31),
|
||||
"validate": _len_validator(31),
|
||||
},
|
||||
@@ -231,7 +300,7 @@ def interactive_update(config_path: str):
|
||||
"type": "input",
|
||||
"message": t("cli.init_folder"),
|
||||
"name": "folder_name",
|
||||
"default": data.get("folder_name", ""),
|
||||
"default": data.get("folder_name", "voxvera"),
|
||||
"transformer": _len_transform(63),
|
||||
"validate": _folder_name_validator,
|
||||
},
|
||||
@@ -239,7 +308,7 @@ def interactive_update(config_path: str):
|
||||
"type": "input",
|
||||
"message": t("cli.init_title"),
|
||||
"name": "title",
|
||||
"default": data.get("title", ""),
|
||||
"default": get_default("title"),
|
||||
"transformer": _len_transform(31),
|
||||
"validate": _len_validator(31),
|
||||
},
|
||||
@@ -247,7 +316,7 @@ def interactive_update(config_path: str):
|
||||
"type": "input",
|
||||
"message": t("cli.init_subtitle"),
|
||||
"name": "subtitle",
|
||||
"default": data.get("subtitle", ""),
|
||||
"default": get_default("subtitle"),
|
||||
"transformer": _len_transform(27),
|
||||
"validate": _len_validator(27),
|
||||
},
|
||||
@@ -255,7 +324,7 @@ def interactive_update(config_path: str):
|
||||
"type": "input",
|
||||
"message": t("cli.init_headline"),
|
||||
"name": "headline",
|
||||
"default": data.get("headline", ""),
|
||||
"default": get_default("headline"),
|
||||
"transformer": _len_transform(31),
|
||||
"validate": _len_validator(31),
|
||||
},
|
||||
@@ -264,7 +333,10 @@ def interactive_update(config_path: str):
|
||||
|
||||
console.rule(t("cli.init_body"))
|
||||
while True:
|
||||
body = open_editor(data.get("content", ""))
|
||||
# Use existing content if present, else fallback to localized landing default
|
||||
initial_content = get_default("content")
|
||||
|
||||
body = open_editor(initial_content)
|
||||
length = len(body)
|
||||
if length > 1000:
|
||||
console.print(f"{t('cli.body_length')}: {length}/1000 {t('cli.body_limit_exceeded')}", style="red")
|
||||
@@ -281,7 +353,7 @@ def interactive_update(config_path: str):
|
||||
"type": "input",
|
||||
"message": t("cli.url_label"),
|
||||
"name": "url",
|
||||
"default": data.get("url", ""),
|
||||
"default": data.get("url", "https://voxvera.org/"),
|
||||
"transformer": _len_transform(200),
|
||||
"validate": _len_validator(200),
|
||||
},
|
||||
@@ -289,10 +361,11 @@ def interactive_update(config_path: str):
|
||||
"type": "input",
|
||||
"message": t("cli.url_message_label"),
|
||||
"name": "url_message",
|
||||
"default": data.get("url_message", ""),
|
||||
"default": get_default("url_message"),
|
||||
"transformer": _len_transform(120),
|
||||
"validate": _len_validator(120),
|
||||
},
|
||||
|
||||
{
|
||||
"type": "input",
|
||||
"message": t("cli.binary_message_label"),
|
||||
@@ -320,44 +393,38 @@ def copy_template(name: str) -> str:
|
||||
return str(dest)
|
||||
|
||||
|
||||
def bundle_portable(dest_zip: Path):
|
||||
"""Bundle the entire VoxVera source, standalone binaries, and dependencies into a ZIP."""
|
||||
def bundle_source(dest_zip: Path):
|
||||
"""Bundle the VoxVera source code, dependencies (vendor), and root scripts."""
|
||||
with zipfile.ZipFile(dest_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
# 1. Include standalone binaries from resources/bin if they exist
|
||||
bin_dir = ROOT / "resources" / "bin"
|
||||
if bin_dir.exists():
|
||||
for bin_file in bin_dir.iterdir():
|
||||
if bin_file.is_file():
|
||||
zipf.write(bin_file, bin_file.name)
|
||||
|
||||
# 2. Include the voxvera/ source directory
|
||||
# 1. Include the voxvera/ source directory (excluding host, bin, pycache)
|
||||
# We DO include vendor here so the source zip is fully functional offline.
|
||||
for root, dirs, files in os.walk(ROOT):
|
||||
if "__pycache__" in root or "host" in root or "vendor" in root or "resources/bin" in root:
|
||||
if "vendor" in root: # We DO want vendor
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
if "__pycache__" in root or "host" in root or "resources/bin" in root:
|
||||
continue
|
||||
for file in files:
|
||||
file_path = Path(root) / file
|
||||
arcname = file_path.relative_to(ROOT.parent)
|
||||
zipf.write(file_path, arcname)
|
||||
|
||||
# 3. Include the run scripts and root files
|
||||
|
||||
# 2. Include the run scripts and root files
|
||||
root_files = [
|
||||
"voxvera-run.sh", "voxvera-install.sh", "README.md",
|
||||
"requirements.txt", "setup.sh", "install.sh"
|
||||
"requirements.txt", "setup.sh", "install.sh", "uninstall.sh",
|
||||
"install.ps1", "uninstall.ps1", "CONTRIBUTING.md", "LICENSE"
|
||||
]
|
||||
for script in root_files:
|
||||
script_path = ROOT.parent / script
|
||||
if script_path.exists():
|
||||
zipf.write(script_path, script)
|
||||
|
||||
# 4. Create a simple Windows batch launcher if it doesn't exist
|
||||
win_launcher = "voxvera.bat"
|
||||
launcher_content = "@echo off\npython -m voxvera.cli manage\npause"
|
||||
zipf.writestr(win_launcher, launcher_content)
|
||||
print(f"Source bundle created at {dest_zip}")
|
||||
|
||||
print(f"Portable bundle created at {dest_zip}")
|
||||
|
||||
def bundle_portable(dest_zip: Path):
|
||||
"""Bundle the entire VoxVera source and dependencies into a ZIP (Viral distribution)."""
|
||||
# For now, we prioritize source code as requested by the user.
|
||||
# We rename it to voxvera-source.zip for clarity.
|
||||
bundle_source(dest_zip)
|
||||
|
||||
|
||||
def build_assets(
|
||||
@@ -369,11 +436,11 @@ def build_assets(
|
||||
config_path = Path(config_path)
|
||||
# generate QR codes (pure Python)
|
||||
generate_qr(config_path, src_dir)
|
||||
|
||||
|
||||
data = load_config(config_path)
|
||||
with open(src_dir / "index-master.html", "r") as fh:
|
||||
html = fh.read()
|
||||
|
||||
|
||||
# Bundle locales for the web engine
|
||||
all_locales = {}
|
||||
locale_files = sorted(glob.glob(str(ROOT / "locales" / "*.json")))
|
||||
@@ -383,15 +450,21 @@ def build_assets(
|
||||
l_data = json.load(lf)
|
||||
all_locales[code] = {
|
||||
"meta": l_data.get("meta", {}),
|
||||
"web": l_data.get("web", {})
|
||||
"web": l_data.get("web", {}),
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
|
||||
|
||||
# Override landing data with user custom config data so JS doesn't overwrite it
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
if field in data and data[field]:
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
|
||||
html = html.replace("{{locales}}", json.dumps(all_locales))
|
||||
|
||||
# 1. Statically replace localization tokens {{t_web_...}}
|
||||
# This ensures the flyer is translated even with JS disabled.
|
||||
lang_data = all_locales.get(data.get("lang", "en"), all_locales.get("en", {}))
|
||||
|
||||
|
||||
# Web tokens (labels, buttons)
|
||||
web_tokens = lang_data.get("web", {})
|
||||
for token, translation in web_tokens.items():
|
||||
@@ -399,48 +472,53 @@ def build_assets(
|
||||
translation = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', str(translation))
|
||||
html = html.replace(f"{{{{t_web_{token}}}}}", translation)
|
||||
|
||||
# Landing tokens (should be cleared for regular flyers)
|
||||
landing_tokens = lang_data.get("landing", {})
|
||||
for token in landing_tokens.keys():
|
||||
html = html.replace(f"{{{{t_landing_{token}}}}}", "")
|
||||
|
||||
# Explicitly handle content and url_message if they were t_landing'd
|
||||
# We want the user's custom content to win
|
||||
html = html.replace("{{t_landing_content}}", "{{content}}")
|
||||
html = html.replace("{{t_landing_url_message}}", "{{url_message}}")
|
||||
# 2. Statically replace landing tokens with user content OR localized defaults
|
||||
# We want the user-provided data from config to take precedence.
|
||||
landing_fields = ["title", "subtitle", "headline", "content", "url_message"]
|
||||
landing_defaults = lang_data.get("landing", {})
|
||||
|
||||
for field in landing_fields:
|
||||
# Use data from config if available, otherwise use localized default
|
||||
value = data.get(field, landing_defaults.get(field, ""))
|
||||
# Support Markdown-style redaction ~~text~~
|
||||
val_str = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', str(value))
|
||||
html = html.replace(f"{{{{t_landing_{field}}}}}", val_str)
|
||||
|
||||
# 2. Statically replace config placeholders {{key}}
|
||||
for key, value in data.items():
|
||||
html = html.replace(f"{{{{{key}}}}}", str(value))
|
||||
val_str = str(value)
|
||||
# Support Markdown-style redaction ~~text~~ for user fields too
|
||||
val_str = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', val_str)
|
||||
html = html.replace(f"{{{{{key}}}}}", val_str)
|
||||
|
||||
folder_name = data["folder_name"]
|
||||
dest = ROOT / "host" / folder_name
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
|
||||
# Update download link to point to our viral portable bundle
|
||||
html = html.replace("download/download.zip", "download/voxvera-portable.zip")
|
||||
|
||||
# Update download link to point to our viral source bundle
|
||||
html = html.replace("download/download.zip", "download/voxvera-source.zip")
|
||||
|
||||
with open(dest / "index.html", "w") as fh:
|
||||
fh.write(html)
|
||||
|
||||
# Create the viral portable bundle
|
||||
|
||||
# Create the viral source bundle
|
||||
download_dir = dest / "download"
|
||||
os.makedirs(download_dir, exist_ok=True)
|
||||
bundle_portable(download_dir / "voxvera-portable.zip")
|
||||
bundle_source(download_dir / "voxvera-source.zip")
|
||||
|
||||
if download_path:
|
||||
shutil.copy(download_path, download_dir / "extra-content.zip")
|
||||
|
||||
|
||||
# Copy config if it's not already there
|
||||
if Path(config_path) != dest / "config.json":
|
||||
shutil.copy(config_path, dest / "config.json")
|
||||
|
||||
|
||||
# Copy QR codes if they exist
|
||||
for qr_file in ["qrcode-content.png", "qrcode-tear-offs.png"]:
|
||||
qr_src = Path(src_dir) / qr_file
|
||||
if qr_src.exists():
|
||||
shutil.copy(qr_src, dest / qr_file)
|
||||
|
||||
|
||||
print(f"Flyer files created under {dest}")
|
||||
|
||||
|
||||
@@ -490,10 +568,29 @@ def get_tor_ports():
|
||||
return socks_port, ctl_port
|
||||
|
||||
|
||||
def serve(config_path: str) -> str | None:
|
||||
if not require_cmd("onionshare-cli"):
|
||||
def _internal_onionshare():
|
||||
"""Hidden command to run the bundled onionshare-cli."""
|
||||
import platform
|
||||
|
||||
# Try to add bundled Tor to PATH so OnionShare can find it
|
||||
system = platform.system().lower()
|
||||
platform_dir = "win" if "windows" in system else "mac" if "darwin" in system else "linux"
|
||||
tor_dir = ROOT / "resources" / "tor" / platform_dir
|
||||
if tor_dir.exists():
|
||||
os.environ["PATH"] = str(tor_dir) + os.pathsep + os.environ.get("PATH", "")
|
||||
|
||||
try:
|
||||
from onionshare_cli import main
|
||||
# sys.argv is ['voxvera', '_internal_onionshare', '--website', ...]
|
||||
# We need to make it look like ['onionshare-cli', '--website', ...]
|
||||
sys.argv = ["onionshare-cli"] + sys.argv[2:]
|
||||
sys.exit(main())
|
||||
except ImportError:
|
||||
print("Error: onionshare-cli not bundled or installed.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def serve(config_path: str) -> str | None:
|
||||
socks, ctl = get_tor_ports()
|
||||
|
||||
folder_name = load_config(config_path)["folder_name"]
|
||||
@@ -508,8 +605,11 @@ def serve(config_path: str) -> str | None:
|
||||
env["TOR_SOCKS_PORT"] = socks
|
||||
env["TOR_CONTROL_PORT"] = ctl
|
||||
|
||||
# Run the bundled onionshare-cli via our hidden command
|
||||
# sys.argv[0] is the current executable (python script or PyInstaller binary)
|
||||
cmd = [
|
||||
"onionshare-cli",
|
||||
sys.argv[0],
|
||||
"_internal_onionshare",
|
||||
"--website",
|
||||
"--public",
|
||||
"--persistent",
|
||||
@@ -520,9 +620,15 @@ def serve(config_path: str) -> str | None:
|
||||
"-v",
|
||||
str(dir_path),
|
||||
]
|
||||
|
||||
# If running via 'python -m voxvera.cli', sys.argv[0] might be the script path,
|
||||
# so we should ensure it's executed correctly if not a PyInstaller binary.
|
||||
if not getattr(sys, 'frozen', False) and sys.argv[0].endswith('.py'):
|
||||
cmd = [sys.executable, sys.argv[0]] + cmd[1:]
|
||||
|
||||
log_fh = open(logfile, "w")
|
||||
proc = subprocess.Popen(cmd, stdout=log_fh, stderr=subprocess.STDOUT, env=env)
|
||||
|
||||
|
||||
pid_file = dir_path / "server.pid"
|
||||
with open(pid_file, "w") as f:
|
||||
f.write(str(proc.pid))
|
||||
@@ -539,7 +645,7 @@ def serve(config_path: str) -> str | None:
|
||||
if proc.poll() is not None:
|
||||
status.stop()
|
||||
console.print(f"[bold red]OnionShare for {folder_name} exited unexpectedly.[/bold red]")
|
||||
console.print(f"[yellow]Common causes: Network blocked, inaccurate system clock, or OnionShare already running.[/yellow]")
|
||||
console.print("[yellow]Common causes: Network blocked, inaccurate system clock, or OnionShare already running.[/yellow]")
|
||||
console.print(f"[dim]See {logfile} for full logs.[/dim]")
|
||||
with open(logfile) as fh:
|
||||
lines = fh.readlines()
|
||||
@@ -548,11 +654,11 @@ def serve(config_path: str) -> str | None:
|
||||
sys.stderr.write(line)
|
||||
pid_file.unlink(missing_ok=True)
|
||||
return None
|
||||
|
||||
|
||||
if os.path.exists(logfile):
|
||||
with open(logfile) as fh:
|
||||
content = fh.read()
|
||||
|
||||
|
||||
# Look for Tor bootstrap progress in logs to give feedback
|
||||
m_progress = _re.findall(r"Bootstrapped (\d+)%", content)
|
||||
if m_progress:
|
||||
@@ -564,16 +670,16 @@ def serve(config_path: str) -> str | None:
|
||||
m = _re.search(r"https?://[a-z0-9]+\.onion", content)
|
||||
if m:
|
||||
onion_url = m.group(0)
|
||||
|
||||
|
||||
# update config with the onion URL in the host directory
|
||||
config_file = dir_path / "config.json"
|
||||
data = load_config(config_file)
|
||||
data["tear_off_link"] = onion_url
|
||||
save_config(data, config_file)
|
||||
|
||||
|
||||
# regenerate assets with the new URL using the host-local config
|
||||
build_assets(str(config_file))
|
||||
|
||||
|
||||
print(f"[{folder_name}] Onion URL: {onion_url}")
|
||||
print(f"[{folder_name}] OnionShare running (PID {proc.pid}).")
|
||||
return onion_url
|
||||
@@ -596,6 +702,7 @@ def _clear_host_dir(dir_path: Path):
|
||||
import zipfile
|
||||
import tempfile
|
||||
|
||||
|
||||
def get_export_dir() -> Path:
|
||||
"""Get the default export directory (~/voxvera-exports)."""
|
||||
export_dir = Path.home() / "voxvera-exports"
|
||||
@@ -622,7 +729,7 @@ def export_site(folder_name: str, output_path: str = None):
|
||||
file_path = Path(root) / file
|
||||
arcname = file_path.relative_to(source_dir)
|
||||
zipf.write(file_path, arcname)
|
||||
|
||||
|
||||
print(f"Exported '{folder_name}' to {output_path}")
|
||||
|
||||
|
||||
@@ -632,7 +739,7 @@ def export_all_sites():
|
||||
if not servers:
|
||||
print("No sites found to export.")
|
||||
return
|
||||
|
||||
|
||||
export_dir = get_export_dir()
|
||||
print(f"Exporting {len(servers)} sites to {export_dir}...")
|
||||
for server in servers:
|
||||
@@ -650,19 +757,19 @@ def import_site(zip_path: str):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zipf:
|
||||
zipf.extractall(tmpdir)
|
||||
|
||||
|
||||
tmp_path = Path(tmpdir)
|
||||
config_file = tmp_path / "config.json"
|
||||
if not config_file.exists():
|
||||
print("Error: Invalid export file. Missing 'config.json'.")
|
||||
return
|
||||
|
||||
|
||||
data = load_config(config_file)
|
||||
folder_name = data.get("folder_name")
|
||||
if not folder_name:
|
||||
print("Error: Invalid config.json. Missing 'folder_name'.")
|
||||
return
|
||||
|
||||
|
||||
dest_dir = ROOT / "host" / folder_name
|
||||
if dest_dir.exists():
|
||||
confirm = inquirer.confirm(message=f"Site '{folder_name}' already exists. Overwrite?").execute()
|
||||
@@ -670,28 +777,28 @@ def import_site(zip_path: str):
|
||||
print("Import cancelled.")
|
||||
return
|
||||
shutil.rmtree(dest_dir)
|
||||
|
||||
|
||||
shutil.copytree(tmp_path, dest_dir)
|
||||
print(f"Successfully imported '{folder_name}' to {dest_dir}")
|
||||
|
||||
|
||||
def import_multiple_sites(source_dir: str = None):
|
||||
"""Import all zip files from a directory (defaults to ~/voxvera-exports)."""
|
||||
|
||||
|
||||
if not source_dir:
|
||||
source_dir = get_export_dir()
|
||||
else:
|
||||
source_dir = Path(source_dir)
|
||||
|
||||
|
||||
if not source_dir.exists():
|
||||
print(f"Error: Directory '{source_dir}' not found.")
|
||||
return
|
||||
|
||||
|
||||
zip_files = sorted(glob.glob(str(source_dir / "*.zip")))
|
||||
if not zip_files:
|
||||
print(f"No zip files found in {source_dir}")
|
||||
return
|
||||
|
||||
|
||||
print(f"Found {len(zip_files)} site(s) to import from {source_dir}...")
|
||||
for zip_path in zip_files:
|
||||
print(f"\nImporting {Path(zip_path).name}...")
|
||||
@@ -725,7 +832,7 @@ def build_docs():
|
||||
# Replace tokens: {{t('path.to.token')}}
|
||||
# This regex looks for {{t('...')}} and {{t("...")}}
|
||||
import re as _re
|
||||
|
||||
|
||||
def replace_token(match):
|
||||
token_path = match.group(1)
|
||||
return t(token_path)
|
||||
@@ -759,7 +866,7 @@ def build_site():
|
||||
return
|
||||
|
||||
print("Refreshing site/ assets...")
|
||||
|
||||
|
||||
# 1. Refresh QR codes using site/config.json
|
||||
from voxvera.build import generate_qr
|
||||
generate_qr(config_path, site_dir)
|
||||
@@ -782,35 +889,59 @@ def build_site():
|
||||
"web": l_data.get("web", {}),
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
|
||||
|
||||
# Override landing data with user custom config data so JS doesn't overwrite it
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
if field in data and data[field]:
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
|
||||
html = html.replace("{{locales}}", json.dumps(all_locales))
|
||||
|
||||
# 1. Statically replace localization tokens
|
||||
# 1. Statically replace localization tokens (web tokens)
|
||||
# Get the language from config if available, else default to en
|
||||
current_lang = data.get("lang", "en")
|
||||
lang_data = all_locales.get(current_lang, all_locales.get("en", {}))
|
||||
|
||||
for category in ["web", "landing"]:
|
||||
tokens = lang_data.get(category, {})
|
||||
for token, translation in tokens.items():
|
||||
# Support Markdown-style redaction ~~text~~ -> <span class="redacted">text</span>
|
||||
translation = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', str(translation))
|
||||
html = html.replace(f"{{{{t_{category}_{token}}}}}", translation)
|
||||
|
||||
# 2. Update relative path for download
|
||||
html = html.replace("download/download.zip", "download/voxvera-portable.zip")
|
||||
web_tokens = lang_data.get("web", {})
|
||||
for token, translation in web_tokens.items():
|
||||
# Support Markdown-style redaction ~~text~~ -> <span class="redacted">text</span>
|
||||
translation = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', str(translation))
|
||||
html = html.replace(f"{{{{t_web_{token}}}}}", translation)
|
||||
|
||||
# 3. Inject site-specific config data (handles any remaining {{key}} placeholders)
|
||||
# 2. Statically replace landing tokens with user content OR localized defaults
|
||||
landing_fields = ["title", "subtitle", "headline", "content", "url_message"]
|
||||
landing_defaults = lang_data.get("landing", {})
|
||||
|
||||
for field in landing_fields:
|
||||
# Use data from config if available, otherwise use localized default
|
||||
value = data.get(field, landing_defaults.get(field, ""))
|
||||
# Support Markdown-style redaction ~~text~~
|
||||
val_str = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', str(value))
|
||||
html = html.replace(f"{{{{t_landing_{field}}}}}", val_str)
|
||||
|
||||
# 3. Update relative path for download
|
||||
html = html.replace("download/download.zip", "download/voxvera-source.zip")
|
||||
|
||||
# 4. Inject site-specific config data (handles any remaining {{key}} placeholders)
|
||||
for key, value in data.items():
|
||||
html = html.replace(f"{{{{{key}}}}}", str(value))
|
||||
|
||||
with open(site_dir / "index.html", "w") as fh:
|
||||
fh.write(html)
|
||||
|
||||
# 3. Create the latest portable bundle in site/download/
|
||||
# 5. Create the download assets in site/download/
|
||||
download_dir = site_dir / "download"
|
||||
os.makedirs(download_dir, exist_ok=True)
|
||||
bundle_portable(download_dir / "voxvera-portable.zip")
|
||||
|
||||
# Bundle source version
|
||||
bundle_source(download_dir / "voxvera-source.zip")
|
||||
|
||||
# Copy standalone binaries if they exist
|
||||
bin_dir = ROOT / "resources" / "bin"
|
||||
if bin_dir.exists():
|
||||
for bin_file in bin_dir.iterdir():
|
||||
if bin_file.is_file():
|
||||
shutil.copy(bin_file, download_dir / bin_file.name)
|
||||
|
||||
print("site/ directory successfully synchronized.")
|
||||
|
||||
@@ -822,10 +953,10 @@ def vendorize():
|
||||
if not req_file.exists():
|
||||
print("Error: requirements.txt not found.")
|
||||
return
|
||||
|
||||
|
||||
os.makedirs(vendor_dir, exist_ok=True)
|
||||
print(f"Vendorizing dependencies into {vendor_dir}...")
|
||||
|
||||
|
||||
try:
|
||||
subprocess.check_call([
|
||||
sys.executable, "-m", "pip", "install",
|
||||
@@ -878,7 +1009,7 @@ def is_server_running(folder_name: str) -> bool:
|
||||
import subprocess
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
output = subprocess.check_output(f'tasklist /FI "PID eq {pid}" /NH', shell=True, stderr=subprocess.STDOUT).decode()
|
||||
output = subprocess.check_output(f"tasklist /FI \"PID eq {pid}\" /NH", shell=True, stderr=subprocess.STDOUT).decode()
|
||||
if "No tasks are running" in output:
|
||||
pid_file.unlink(missing_ok=True)
|
||||
return False
|
||||
@@ -892,7 +1023,7 @@ def is_server_running(folder_name: str) -> bool:
|
||||
except OSError:
|
||||
pid_file.unlink(missing_ok=True)
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(["ps", "-p", str(pid), "-o", "command="], stderr=subprocess.STDOUT).decode().lower()
|
||||
if "onionshare" in output or "python" in output or "tor" in output:
|
||||
@@ -935,15 +1066,15 @@ def manage_servers():
|
||||
from InquirerPy import inquirer
|
||||
from InquirerPy.base.control import Choice
|
||||
console = Console()
|
||||
|
||||
|
||||
while True:
|
||||
servers = get_servers()
|
||||
|
||||
|
||||
choices = []
|
||||
for s in servers:
|
||||
running = is_server_running(s)
|
||||
status_text = "[ON] " if running else "[OFF]"
|
||||
|
||||
|
||||
label = f"{status_text} {s}"
|
||||
if running:
|
||||
try:
|
||||
@@ -954,7 +1085,7 @@ def manage_servers():
|
||||
except Exception:
|
||||
pass
|
||||
choices.append(Choice(s, label))
|
||||
|
||||
|
||||
choices.insert(0, Choice("create_new", t("cli.manage_create_new")))
|
||||
if servers:
|
||||
choices.append(Choice("start_all", t("cli.manage_start_all")))
|
||||
@@ -962,23 +1093,34 @@ def manage_servers():
|
||||
choices.append(Choice("export_all", t("cli.manage_export_all")))
|
||||
choices.append(Choice("import_multiple", t("cli.manage_import_multiple")))
|
||||
choices.append(Choice(None, t("cli.manage_exit")))
|
||||
|
||||
|
||||
selected = inquirer.select(
|
||||
message=t("cli.manage_select_server"),
|
||||
choices=choices
|
||||
).execute()
|
||||
|
||||
|
||||
if selected is None:
|
||||
break
|
||||
|
||||
|
||||
if selected == "create_new":
|
||||
config_path = ROOT / "src" / "config.json"
|
||||
interactive_update(str(config_path))
|
||||
build_assets(str(config_path))
|
||||
# Reset config from template for a fresh start
|
||||
try:
|
||||
template_config = _template_res("voxvera", "config.json")
|
||||
with resources.as_file(template_config) as tc:
|
||||
shutil.copy(tc, config_path)
|
||||
except Exception as e:
|
||||
console.print(f"[yellow]Warning: Could not reset config from template: {e}[/yellow]")
|
||||
|
||||
try:
|
||||
interactive_update(str(config_path))
|
||||
build_assets(str(config_path))
|
||||
serve(str(config_path))
|
||||
except SystemExit:
|
||||
pass
|
||||
except (KeyboardInterrupt, Exception) as e:
|
||||
if not isinstance(e, KeyboardInterrupt):
|
||||
console.print(f"[red]Error creating site: {e}[/red]")
|
||||
else:
|
||||
console.print("\n[yellow]Site creation cancelled.[/yellow]")
|
||||
continue
|
||||
|
||||
if selected == "start_all":
|
||||
@@ -992,7 +1134,7 @@ def manage_servers():
|
||||
results.append((s, url))
|
||||
except Exception as e:
|
||||
console.print(f"[red]{t('cli.manage_startup_summary')}: {e}[/red]")
|
||||
|
||||
|
||||
if results:
|
||||
console.rule(t("cli.manage_startup_summary"))
|
||||
for s, url in results:
|
||||
@@ -1005,15 +1147,15 @@ def manage_servers():
|
||||
if is_server_running(s):
|
||||
stop_server(s)
|
||||
continue
|
||||
|
||||
|
||||
if selected == "export_all":
|
||||
export_all_sites()
|
||||
continue
|
||||
|
||||
|
||||
if selected == "import_multiple":
|
||||
import_multiple_sites()
|
||||
continue
|
||||
|
||||
|
||||
running = is_server_running(selected)
|
||||
action_choices = [
|
||||
Choice("toggle", t("cli.manage_action_toggle_stop") if running else t("cli.manage_action_toggle_start")),
|
||||
@@ -1021,12 +1163,12 @@ def manage_servers():
|
||||
Choice("delete", t("cli.manage_action_delete")),
|
||||
Choice(None, t("cli.manage_action_back"))
|
||||
]
|
||||
|
||||
|
||||
action = inquirer.select(
|
||||
message=t("cli.manage_action_menu", name=selected),
|
||||
choices=action_choices
|
||||
).execute()
|
||||
|
||||
|
||||
if action == "toggle":
|
||||
if running:
|
||||
stop_server(selected)
|
||||
@@ -1049,7 +1191,7 @@ def choose_language(current_lang: str = None) -> str:
|
||||
# List available locales
|
||||
locale_files = sorted(glob.glob(str(ROOT / "locales" / "*.json")))
|
||||
choices = []
|
||||
|
||||
|
||||
# Use fallback if no locales folder (e.g. first run/dev)
|
||||
if not locale_files:
|
||||
return "en"
|
||||
@@ -1111,7 +1253,7 @@ def main(argv=None):
|
||||
p_export.add_argument("--output", help="Custom output path for the zip")
|
||||
|
||||
sub.add_parser("export-all", help="Export all sites and their keys to ~/voxvera-exports")
|
||||
|
||||
|
||||
p_import = sub.add_parser("import", help="Import a site and its keys from a zip archive")
|
||||
p_import.add_argument("zip_path", help="Path to the exported zip file")
|
||||
|
||||
@@ -1133,8 +1275,14 @@ def main(argv=None):
|
||||
sub.add_parser("build-docs", help="Generate localized documentation from templates")
|
||||
sub.add_parser("build-site", help="Refresh the main site/ directory assets and bundle")
|
||||
sub.add_parser("manage", help="Manage VoxVera servers interactively")
|
||||
sub.add_parser("_internal_onionshare", help=argparse.SUPPRESS)
|
||||
|
||||
args, unknown_args = parser.parse_known_args(argv)
|
||||
|
||||
if args.command == "_internal_onionshare":
|
||||
_internal_onionshare()
|
||||
return
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
config_path = Path(args.config).resolve()
|
||||
|
||||
# Determine language
|
||||
@@ -1154,7 +1302,7 @@ def main(argv=None):
|
||||
if config_path.exists():
|
||||
config_data["lang"] = current_lang
|
||||
save_config(config_data, str(config_path))
|
||||
|
||||
|
||||
# Load the locale
|
||||
load_locale(current_lang or "en")
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "Vox Vera Printable Flyers",
|
||||
"folder_name": "voxvera",
|
||||
"title": "TOP SECRET",
|
||||
"subtitle": "DO <span class=\"redacted\">NOT</span> DISTRIBUTE",
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser.",
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~لا~~ للنشر",
|
||||
"headline": "عملية VOX VERA",
|
||||
"content": "تحرر من الرقابة مع VoxVera. أداة تسويق عصابات مجهولة ونشر رسائل. سواء عبر الإنترنت أو في العالم المادي، يمكنها تمكينك من نشر أفكارك بجرأة، محمية بإخفاء الهوية الكامل (إذا كنت تستضيف عبر Tor). قم بتحميل الكود، وصمم موقع منشور، واستضيفه عبر الإنترنت وقم بتضخيم رسالتك.\n\nاستخدم القوة الميمية لمشاركة أفكارك في مدرستك، أو مكان عملك، أو مجتمعاتك عبر الإنترنت، أو حتى على مستوى العالم. تضمن VoxVera تردد صدى رسالتك بعيداً وعلى نطاق واسع، مع أقسام قابلة للنزع تتميز بروابط فريدة وأكواد QR لسهولة إعادة الطباعة.\n\nيمكن الحفاظ على الخصوصية. يمكن مشاركة المنشورات عبر شبكة Tor، مما يحمي المضيف والمستخدمين من الرقابة.\n\nانضم إلينا في ثورة تقدر الحقيقة والشفافية. معاً، يمكننا بناء شبكة من المواطنين المستنيرين الذين لا يخشون التحدث.",
|
||||
"url_message": "احصل على VoxVera وابدأ بنشر رسالتك."
|
||||
"url_message": "احصل على VoxVera وابدأ بنشر رسالتك.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@
|
||||
"subtitle": "~~NICHT~~ VERBREITEN",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Befreien Sie sich von Zensur mit VoxVera. Ein anonymes Guerilla-Marketing- und Nachrichtenverbreitungs-Tool. Ob online oder in der physischen Welt, es befähigt Sie, Ihre Ideen mutig zu verbreiten, geschützt durch vollständige Anonymität (wenn Sie über Tor hosten). Laden Sie den Code herunter, entwerfen Sie eine Flyer-Website, hosten Sie diese online und verstärken Sie Ihre Botschaft.\n\nNutzen Sie memetische Kraft, um Ihre Ideen in Ihrer Schule, an Ihrem Arbeitsplatz, in Online-Communities oder sogar weltweit zu teilen. VoxVera stellt sicher, dass Ihre Botschaft weit und breit Resonanz findet, mit Abreißabschnitten, die über einzigartige URLs und QR-Codes für einen einfachen Nachdruck verfügen.\n\nPrivatsphäre kann gewahrt bleiben. Flyer können über das Tor-Network geteilt werden, was Hoster und Nutzer vor Zensur schützt.\n\nSchließen Sie sich uns in einer Revolution an, die Wahrheit und Transparenz schätzt. Gemeinsam können wir ein Netzwerk informierter Bürger aufbauen, die keine Angst haben, ihre Meinung zu sagen.",
|
||||
"url_message": "Holen Sie sich VoxVera und beginnen Sie, Ihre Nachricht zu verbreiten."
|
||||
"url_message": "Holen Sie sich VoxVera und beginnen Sie, Ihre Nachricht zu verbreiten.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,13 +79,14 @@
|
||||
"tear_off_footer": "click the button below to print",
|
||||
"built_with": "built with",
|
||||
"print_button": "Print this page",
|
||||
"download_button": "Download Tool & Source"
|
||||
"download_button": "Download Source Code"
|
||||
},
|
||||
"landing": {
|
||||
"title": "TOP SECRET",
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.\n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.\n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Get VoxVera and start spreading your message."
|
||||
"url_message": "Get VoxVera and start spreading your message.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@
|
||||
"subtitle": "~~NO~~ DISTRIBUIR",
|
||||
"headline": "OPERACIÓN VOX VERA",
|
||||
"content": "Libérese de la censura con VoxVera. Una herramienta anónima de marketing de guerrilla y difusión de mensajes. Ya sea en línea o en el mundo físico, puede empoderarlo para difundir sus ideas con audacia, protegido por un anonimato completo (si hospeda a través de Tor). Descargue el código, diseñe un sitio de volantes, hospédelo en línea y amplifique su mensaje.\n\nUse el poder memético para compartir sus ideas en su escuela, lugar de trabajo, comunidades en línea o incluso globalmente. VoxVera asegura que su mensaje resuene a lo largo und ancho, con secciones desprendibles que cuentan con URLs y códigos QR únicos para una fácil reimpresión.\n\nLa privacidad puede mantenerse. Los volantes se pueden compartir a través de la red Tor, protegiendo al anfitrión y a los usuarios de la censura.\n\nÚnase a nosotros en una revolución que valora la verdad y la transparencia. Juntos, podemos construir una red de ciudadanos informados que no temen hablar.",
|
||||
"url_message": "Obtenga VoxVera y comience a difundir su mensaje."
|
||||
"url_message": "Obtenga VoxVera y comience a difundir su mensaje.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~عدم~~ انتشار",
|
||||
"headline": "عملیات VOX VERA",
|
||||
"content": "با VoxVera از بند سانسور رها شوید. ابزاری ناشناس برای بازاریابی چریکی و گسترش پیام. چه در فضای مجازی و چه در دنیای فیزیکی، این ابزار میتواند شما را برای گسترش جسورانه ایدههایتان توانمند کند، در حالی که توسط ناشناسی کامل محافظت میشوید (اگر از طریق Tor میزبانی کنید). کد را دانلود کنید، یک سایت اعلامیه طراحی کنید، آن را آنلاین میزبانی کنید و صدای خود را تقویت کنید.\n\nاز قدرت میمها برای به اشتراک گذاشتن ایدههای خود در مدرسه، محل کار، انجمنهای آنلاین یا حتی در سطح جهانی استفاده کنید. VoxVera تضمین میکند که پیام شما در دوردستها طنینانداز شود، با بخشهای جداشونده دارای URLهای منحصر به فرد و کدهای QR برای چاپ مجدد آسان.\n\nحریم خصوصی میتواند حفظ شود. اعلامیهها را میتوان از طریق شبکه Tor به اشتراک گذاشت و از میزبان و کاربران در برابر سانسور محافظت کرد.\n\nدر انقلابی که برای حقیقت و شفافیت ارزش قائل است به ما بپیوندید. با هم، میتوانیم شبکهای از شهروندان آگاه بسازیم که از سخن گفتن نمیهراسند.",
|
||||
"url_message": "VoxVera را دریافت کنید و گسترش پیام خود را آغاز کنید."
|
||||
"url_message": "VoxVera را دریافت کنید و گسترش پیام خود را آغاز کنید.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~NE PAS~~ DISTRIBUER",
|
||||
"headline": "OPÉRATION VOX VERA",
|
||||
"content": "Libérez-vous de la censure avec VoxVera. Un outil de marketing de guérilla anonyme et de diffusion de messages. Que ce soit en ligne ou dans le monde physique, il peut vous donner le pouvoir de diffuser vos idées avec audace, à l'abri d'un anonymat complet (si vous hébergez via Tor). Téléchargez le code, concevez un site de flyers, hébergez-le en ligne et amplifiez votre message.\n\nUtilisez le pouvoir mémétique pour partager vos idées à l'école, au travail, dans les communautés en ligne ou même à l'échelle mondiale. VoxVera garantit que votre message résonne partout, avec des sections détachables comportant des URL uniques et des codes QR pour une réimpression facile.\n\nLa vie privée peut être préservée. Les flyers peuvent être partagés via le réseau Tor, protégeant l'hébergeur et les utilisateurs de la censure.\n\nRejoignez-nous dans une révolution qui valorise la vérité et la transparence. Ensemble, nous pouvons construire un réseau de citoyens informés qui n'ont pas peur de s'exprimer.",
|
||||
"url_message": "Obtenez VoxVera et commencez à diffuser votre message."
|
||||
"url_message": "Obtenez VoxVera et commencez à diffuser votre message.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~לא~~ להפצה",
|
||||
"headline": "מבצע VOX VERA",
|
||||
"content": "השתחרר מהצנזורה עם VoxVera. כלי אנונימי לשיווק גרילה והפצת מסרים. בין אם באינטרנט ובין אם בעולם הפיזי, הוא יכול להעצים אותך להפיץ את הרעיונות שלך בתעוזה, מוגן באנונימיות מלאה (אם אתה מארח דרך Tor). הורד את הקוד, עצב אתר פלייר, ארח אותו אונליין והגבר את המסר שלך.\n\nהשתמש בכוח הממטי כדי לשתף את הרעיונות שלך בבית הספר, במקום העבודה, בקהילות מקוונות או אפילו ברמה עולמית. VoxVera מבטיחה שהמסר שלך יהדהד למרחקים, עם חלקים נתלשים הכוללים כתובות URL ייחודיות וקודי QR להדפסה חוזרת קלה.\n\nניתן לשמור על פרטיות. ניתן לשתף פליירים דרך רשת Tor, ובכך להגן על המארח ועל המשתמשים מפני צנזורה.\n\nהצטרפו אלינו למהפכה שמעריכה אמת ושקיפות. יחד, נוכל לבנות רשת של אזרחים מושכלים שאינם פוחדים להתבטא.",
|
||||
"url_message": "קבלו את VoxVera והתחילו להפיץ את המסר שלכם."
|
||||
"url_message": "קבלו את VoxVera והתחילו להפיץ את המסר שלכם.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~वितरित न करें~~",
|
||||
"headline": "ऑपरेशन VOX VERA",
|
||||
"content": "VoxVera के साथ सेंसरशिप से मुक्त हों। एक गुमनाम गुरिल्ला मार्केटिंग और संदेश फैलाने वाला टूल। चाहे ऑनलाइन हो या भौतिक दुनिया में, यह आपको पूर्ण गुमनामी (यदि आप Tor पर होस्ट करते हैं) द्वारा सुरक्षित रहते हुए अपने विचारों को साहसपूर्वक फैलाने का अधिकार दे सकता है। कोड डाउनलोड करें, एक फ्लायर साइट डिज़ाइन करें, इसे ऑनलाइन होस्ट करें और अपने संदेश को बढ़ाएं।\n\nअपने विचारों को अपने स्कूल, कार्यस्थल, ऑनलाइन समुदायों या विश्व स्तर पर साझा करने के लिए मेमेटिक शक्ति का उपयोग करें। VoxVera यह सुनिश्चित करता है कि आपका संदेश दूर-दूर तक गूंजे, जिसमें आसान पुनर्मुद्रण के लिए अद्वितीय URL और QR कोड वाले टियर-ऑफ सेक्शन हैं।\n\nगोपनीयता बनाए रखी जा सकती है। फ्लायर्स को Tor नेटवर्क के माध्यम से साझा किया जा सकता है, जिससे होस्ट और उपयोगकर्ताओं को सेंसरशिप से बचाया जा सकता है।\n\nएक ऐसी क्रांति में हमारे साथ जुड़ें जो सच्चाई और पारदर्शिता को महत्व देती है। साथ मिलकर, हम उन सूचित नागरिकों का एक नेटवर्क बना सकते हैं जो बोलने से नहीं डरते।",
|
||||
"url_message": "VoxVera प्राप्त करें और अपना संदेश फैलाना शुरू करें।"
|
||||
"url_message": "VoxVera प्राप्त करें और अपना संदेश फैलाना शुरू करें।",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~配布禁止~~",
|
||||
"headline": "作戦名:VOX VERA",
|
||||
"content": "VoxVeraで検閲から解放されましょう。これは、匿名のゲリラマーケティングおよびメッセージ拡散ツールです。オンラインでも物理的な世界でも、Torを介したホスティングによって完全な匿名性に守られながら、自分の考えを大胆に広めることができます。コードをダウンロードし、フライヤーサイトをデザインし、オンラインで公開して、あなたのメッセージを増幅させてください。\n\nミームの力を利用して、学校、職場、オンラインコミュニティ、あるいは世界中にあなたのアイデアを共有しましょう。VoxVeraは、再印刷が簡単な独自のURLとQRコードを備えた切り取りセクションにより、あなたのメッセージが広く遠くまで響くことを保証します。\n\nプライバシーは維持されます。フライヤーはTorネットワークを介して共有でき、ホスト側とユーザー側の両方を検閲から守ります。\n\n真実と透明性を尊重する革命に加わりましょう。共に、声を上げることを恐れない、知識豊富な市民のネットワークを構築しましょう。",
|
||||
"url_message": "VoxVeraを入手して、メッセージの拡散を始めましょう。"
|
||||
"url_message": "VoxVeraを入手して、メッセージの拡散を始めましょう。",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~NÃO~~ DISTRIBUIR",
|
||||
"headline": "OPERAÇÃO VOX VERA",
|
||||
"content": "Liberte-se da censura com o VoxVera. Uma ferramenta anônima de marketing de guerrilha e propagação de mensagens. Seja online ou no mundo físico, ela pode capacitá-lo a espalhar suas ideias com audácia, protegido por anonimato completo (se você hospedar via Tor). Baixe o código, projete um site de panfleto, hospede-o online e amplifique sua mensagem.\n\nUse o poder memético para compartilhar suas ideias em sua escola, local de trabalho, comunidades online ou até mesmo globalmente. O VoxVera garante que sua mensagem ressoe longe e amplamente, com seções destacáveis apresentando URLs e códigos QR exclusivos para fácil reimpressão.\n\nA privacidade pode ser mantida. Panfletos podem ser compartilhados via rede Tor, protegendo quem hospeda e quem usa contra censura.\n\nJunte-se a nós em uma revolução que valoriza a verdade e a transparência. Juntos, podemos construir uma rede de cidadãos informados que não têm medo de falar.",
|
||||
"url_message": "Obtenha o VoxVera e comece a espalhar sua mensagem."
|
||||
"url_message": "Obtenha o VoxVera e comece a espalhar sua mensagem.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~НЕ~~ РАСПРОСТРАНЯТЬ",
|
||||
"headline": "ОПЕРАЦИЯ VOX VERA",
|
||||
"content": "Освободитесь от цензуры с помощью VoxVera. Анонимный инструмент для партизанского маркетинга и распространения сообщений. Будь то в сети или в физическом мире, он дает вам возможность смело распространять свои идеи, будучи защищенным полной анонимностью (при хостинге через Tor). Скачайте код, создайте сайт-листовку, разместите его в сети и усильте свой голос.\n\nИспользуйте силу мемов, чтобы поделиться своими идеями в школе, на работе, в онлайн-сообществах или даже по всему миру. VoxVera гарантирует, что ваше сообщение найдет отклик повсюду, благодаря отрывным разделам с уникальными URL-адресами и QR-кодами для легкой перепечатки.\n\nКонфиденциальность может быть сохранена. Листовками можно делиться через сеть Tor, защищая хостера и пользователей от цензуры.\n\nПрисоединяйтесь к нам в революции, которая ценит правду и прозрачность. Вместе мы сможем построить сеть информированных граждан, которые не боятся высказываться.",
|
||||
"url_message": "Получите VoxVera и начните распространять свое сообщение."
|
||||
"url_message": "Получите VoxVera и начните распространять свое сообщение.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~USISAMBAZE~~",
|
||||
"headline": "OPARESHENI VOX VERA",
|
||||
"content": "Jikomboe kutoka kwa udhibiti ukitumia VoxVera. Zana isiyojulikana ya uuzaji wa mshituko na kueneza ujumbe. Iwe mtandaoni au katika ulimwengu wa kimwili, inaweza kukuwezesha kueneza mawazo yako kwa ujasiri, ukilindwa na kutokujulikana kabisa (ikiwa unahifadhi kupitia Tor). Pakua nambari ya siri, tengeneza tovuti ya kipeperushi, ihifadhi mtandaoni na uimarishe ujumbe wako.\n\nTumia nguvu ya meme kushiriki mawazo yako katika shule yako, mahali pa kazi, jumuiya za mtandaoni, au hata duniani kote. VoxVera inahakikisha ujumbe wako unavuma mbali na kote, kukiwa na sehemu zinazoweza kutolewa zenye URL za kipekee na QR codes kwa uchapishaji rahisi.\n\nFaragha inaweza kudumishwa. Vipengele vinaweza kushirikiwa kupitia mtandao wa Tor, kumlinda mhudumu na watumiaji dhidi ya udhibiti.\n\nJiunge nasi katika mapinduzi yanayothamini ukweli na uwazi. Kwa pamoja, tunaweza kujenga mtandao wa raia wenye ufahamu ambao hawaogopi kuzungumza.",
|
||||
"url_message": "Pata VoxVera na uanze kueneza ujumbe wako."
|
||||
"url_message": "Pata VoxVera na uanze kueneza ujumbe wako.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~DAĞITMAYIN~~",
|
||||
"headline": "VOX VERA OPERASYONU",
|
||||
"content": "VoxVera ile sansürden kurtulun. Anonim bir gerilla pazarlama ve mesaj yayma aracı. İster çevrimiçi ister fiziksel dünyada olsun, Tor üzerinden barındırma yaparak tam anonimlik koruması altında fikirlerinizi cesurca yaymanızı sağlar. Kodu indirin, bir ilan sitesi tasarlayın, çevrimiçi barındırın ve mesajınızı güçlendirin.\n\nFikirlerinizi okulunuzda, iş yerinizde, çevrimiçi topluluklarda ve hatta küresel çapta paylaşmak için memetik gücü kullanın. VoxVera, benzersiz URL'ler ve kolayca yeniden yazdırma için QR kodları içeren yırtılabilir bölümleriyle mesajınızın geniş kitlelere ulaşmasını sağlar.\n\nGizlilik korunabilir. İlanlar Tor ağı üzerinden paylaşılabilir, böylece hem barındıran hem de kullanıcılar sansürden korunur.\n\nGerçeğe ve şeffaflığa değer veren bir devrime katılın. Birlikte, sesini yükseltmekten korkmayan bilinçli vatandaşlardan oluşan bir ağ kurabiliriz.",
|
||||
"url_message": "VoxVera'yı edinin ve mesajınızı yaymaya başlayın."
|
||||
"url_message": "VoxVera'yı edinin ve mesajınızı yaymaya başlayın.",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@
|
||||
"subtitle": "~~切勿~~ 散布",
|
||||
"headline": "VOX VERA 行动",
|
||||
"content": "使用 VoxVera 摆脱审查。一个匿名的游击营销和消息传播工具。无论是线上还是在现实世界中,它都能赋予你大胆传播想法的力量,并受到完全匿名的保护(如果你通过 Tor 托管)。下载代码,设计传单站点,将其托管在网上并放大你的声音。\n\n利用模因的力量在你的学校、工作场所、在线社区甚至全球范围内分享你的想法。VoxVera 通过带有独特 URL 和 QR 码的可撕下部分确保你的消息产生深远共鸣,方便再次打印。\n\n隐私可以得到维护。传单可以通过 Tor 网络共享,保护托管者和用户免受审查。\n\n加入我们这场重视真相和透明度的革命。齐心协力,我们可以建立一个不惧怕发声的知情公民网络。",
|
||||
"url_message": "获取 VoxVera 并开始传播你的消息。"
|
||||
"url_message": "获取 VoxVera 并开始传播你的消息。",
|
||||
"name": "Vox Vera Printable Flyers"
|
||||
}
|
||||
}
|
||||
}
|
||||
Regular → Executable
BIN
Binary file not shown.
@@ -330,6 +330,51 @@
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
background-color: #f9f9f9;
|
||||
min-width: 200px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
border: 1px solid #000;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dropdown-content a:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown:hover .download-button {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
// VoxVera Multi-Language Engine
|
||||
@@ -531,7 +576,9 @@
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="print-button" onclick="window.print()" data-i18n="web.print_button">{{t_web_print_button}}</button>
|
||||
<a class="download-button" href="download/download.zip" download data-i18n="web.download_button">{{t_web_download_button}}</a>
|
||||
<a id="download-link" href="download/voxvera-source.zip" download>
|
||||
<button class="download-button" data-i18n="web.download_button">{{t_web_download_button}}</button>
|
||||
</a>
|
||||
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "Vox Vera Printable Flyers",
|
||||
"folder_name": "voxvera",
|
||||
"title": "TOP SECRET",
|
||||
"subtitle": "DO <span class=\\\"redacted\\\">NOT</span> DISTRIBUTE",
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.\\n\\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.\\n\\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\\n\\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser.",
|
||||
|
||||
Reference in new issue
Block a user