mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 17:50:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
283d365744 | ||
|
|
cc569a0f67 | ||
|
|
68b155e901 | ||
|
|
7f533560c9 | ||
|
|
3fe62f94be | ||
|
|
30ca68895f | ||
|
|
b3ee6869a2 | ||
|
|
dff82c6abd | ||
|
|
98e51c4712 | ||
|
|
4567563b1f | ||
|
|
bded7b870f | ||
|
|
cbcc7b1d15 | ||
|
|
da33c5cfd1 | ||
|
|
e6d12ba53e | ||
|
|
4717304e1e | ||
|
|
70d214a702 | ||
|
|
0400199795 | ||
|
|
00d55a2fda |
No files matched your search
@@ -10,22 +10,18 @@ jobs:
|
||||
build:
|
||||
name: Build for ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: write
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-22.04, windows-latest, macos-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
artifact_name: voxvera-linux
|
||||
asset_name: voxvera-linux
|
||||
- os: ubuntu-22.04
|
||||
artifact_name: voxvera-linux-x86_64
|
||||
- os: windows-latest
|
||||
artifact_name: voxvera-windows.exe
|
||||
asset_name: voxvera-windows.exe
|
||||
- os: macos-latest
|
||||
artifact_name: voxvera-macos
|
||||
asset_name: voxvera-macos
|
||||
artifact_name: voxvera-macos-x86_64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -61,37 +57,43 @@ jobs:
|
||||
voxvera/cli.py
|
||||
|
||||
- name: Build .deb (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
mkdir -p voxvera/resources/bin
|
||||
cp dist/${{ matrix.artifact_name }} voxvera/resources/bin/voxvera-linux-x86_64
|
||||
chmod +x packaging/build_deb.sh
|
||||
./packaging/build_deb.sh dist/${{ matrix.artifact_name }}
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
mkdir -p voxvera/resources/bin
|
||||
cp dist/${{ matrix.artifact_name }} voxvera/resources/bin/voxvera-linux-x86_64
|
||||
chmod +x packaging/build_appimage.sh
|
||||
./packaging/build_appimage.sh
|
||||
./packaging/build_appimage.sh dist/${{ matrix.artifact_name }}
|
||||
|
||||
- name: Build Flatpak (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
mkdir -p voxvera/resources/bin
|
||||
cp dist/${{ matrix.artifact_name }} voxvera/resources/bin/voxvera-linux-x86_64
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y flatpak flatpak-builder
|
||||
chmod +x packaging/build_flatpak.sh
|
||||
./packaging/build_flatpak.sh || echo "Flatpak build failed"
|
||||
./packaging/build_flatpak.sh dist/${{ matrix.artifact_name }} || echo "Flatpak build failed"
|
||||
|
||||
- name: Build .dmg (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
mkdir -p voxvera/resources/bin
|
||||
cp dist/${{ matrix.artifact_name }} voxvera/resources/bin/voxvera-macos-x86_64
|
||||
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 }}
|
||||
name: binaries-${{ matrix.os }}
|
||||
path: |
|
||||
dist/${{ matrix.artifact_name }}
|
||||
voxvera/resources/bin/*.deb
|
||||
@@ -99,16 +101,30 @@ jobs:
|
||||
voxvera/resources/bin/*.AppImage
|
||||
voxvera/resources/bin/*.flatpak
|
||||
|
||||
publish:
|
||||
name: Create GitHub Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Flatten artifacts for release
|
||||
run: |
|
||||
mkdir -p release_assets
|
||||
find artifacts -type f -exec cp {} release_assets/ \;
|
||||
ls -R release_assets/
|
||||
|
||||
- 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
|
||||
voxvera/resources/bin/*.AppImage
|
||||
voxvera/resources/bin/*.flatpak
|
||||
release_assets/*
|
||||
fail_on_unmatched_files: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
jobs:
|
||||
lint-test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
@@ -16,8 +17,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest
|
||||
pip install -e .
|
||||
pip install flake8
|
||||
pip install -e ".[test]"
|
||||
- name: Lint
|
||||
run: flake8 voxvera tests --exclude=voxvera/vendor --ignore=E501,W503,W293,E402,E306,E704,E261,F841,E302,F401,E401,F541
|
||||
- name: Test
|
||||
|
||||
@@ -8,8 +8,6 @@ jobs:
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
VOXVERA_E2E_OFFLINE: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
@@ -20,7 +18,8 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y tor
|
||||
pip install ".[e2e]"
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e ".[e2e]"
|
||||
- name: Run end-to-end tests
|
||||
run: ci/test-e2e.sh
|
||||
- name: Upload logs
|
||||
|
||||
+100
-26
@@ -1,86 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Robustness: ensure we are in the project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
LOG_DIR="$(pwd)/ci-logs"
|
||||
mkdir -p "$LOG_DIR"
|
||||
# Use a subshell for tee to avoid issues with some shell environments
|
||||
exec > >(tee "$LOG_DIR/run.log") 2>&1
|
||||
|
||||
echo "Starting E2E verification at $(date)"
|
||||
|
||||
# Install VoxVera
|
||||
if [ -n "${VOXVERA_BIN:-}" ]; then
|
||||
if [ -n "${VOXVERA_BIN:-}" ] && [ -f "$VOXVERA_BIN" ]; then
|
||||
voxvera_cmd="$VOXVERA_BIN"
|
||||
echo "Using provided VoxVera binary: $voxvera_cmd" >&2
|
||||
else
|
||||
{ time pip install --break-system-packages -e .; } 2>&1
|
||||
voxvera_cmd="$(command -v voxvera)"
|
||||
echo "Installing VoxVera in editable mode..."
|
||||
python3 -m pip install --break-system-packages -e ".[e2e]"
|
||||
voxvera_cmd="$(command -v voxvera || echo "$HOME/.local/bin/voxvera")"
|
||||
fi
|
||||
|
||||
echo "VoxVera command: $voxvera_cmd"
|
||||
|
||||
# Generate demo flyer
|
||||
echo "Initializing demo flyer..."
|
||||
"$voxvera_cmd" --lang en init --template voxvera <<EOI
|
||||
DemoUser
|
||||
demosite
|
||||
voxvera
|
||||
EOI
|
||||
|
||||
echo "Building demo flyer..."
|
||||
"$voxvera_cmd" --lang en build
|
||||
|
||||
# Verify build output exists in host/
|
||||
folder_name=$(python3 -c "import json; print(json.load(open('voxvera/host/voxvera/config.json'))['folder_name'])")
|
||||
folder_name="voxvera"
|
||||
host_dir="voxvera/host/$folder_name"
|
||||
|
||||
if [ ! -d "$host_dir" ]; then
|
||||
echo "CRITICAL: Host directory $host_dir not found!" >&2
|
||||
ls -R voxvera/host/ || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ls -R "$host_dir" >>"$LOG_DIR/tree.txt"
|
||||
|
||||
# Verify essential files
|
||||
for f in index.html config.json; do
|
||||
[ -f "$host_dir/$f" ] || { echo "Missing $f in $host_dir" >&2; exit 1; }
|
||||
if [ ! -f "$host_dir/$f" ]; then
|
||||
echo "CRITICAL: Missing $f in $host_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "Build output verified in $host_dir"
|
||||
|
||||
# Run pytest unit tests
|
||||
echo "Running pytest unit tests..."
|
||||
python3 -m pytest tests/ -v 2>&1 | tee "$LOG_DIR/pytest.log"
|
||||
|
||||
# Optional network tests
|
||||
if [ "${VOXVERA_E2E_OFFLINE:-}" != "1" ]; then
|
||||
# Start Tor
|
||||
tor >"$LOG_DIR/tor.log" 2>&1 &
|
||||
echo "Starting network-dependent tests..."
|
||||
|
||||
# GitHub Actions Ubuntu runners have Tor pre-installed and running as a service.
|
||||
# We stop it to ensure we can run our own instance with known configuration on default ports.
|
||||
echo "Stopping system Tor service if running..."
|
||||
sudo systemctl stop tor || true
|
||||
|
||||
# Kill any lingering tor processes
|
||||
sudo pkill -x tor || true
|
||||
|
||||
echo "Starting a fresh Tor instance on default ports (9050/9051)..."
|
||||
mkdir -p "$LOG_DIR/tor_data"
|
||||
tor --SocksPort 9050 --ControlPort 9051 --CookieAuthentication 0 --DataDirectory "$LOG_DIR/tor_data" >"$LOG_DIR/tor.log" 2>&1 &
|
||||
TOR_PID=$!
|
||||
sleep 10
|
||||
|
||||
# Wait for Tor to bootstrap
|
||||
echo "Waiting for Tor to bootstrap..."
|
||||
for i in {1..60}; do
|
||||
if grep -q "Bootstrapped 100%" "$LOG_DIR/tor.log"; then
|
||||
echo "Tor bootstrapped successfully."
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Start OnionShare
|
||||
echo "Starting OnionShare..."
|
||||
# Explicitly set environment variables that OnionShare-cli respects
|
||||
export TOR_SOCKS_PORT=9050
|
||||
export TOR_CONTROL_PORT=9051
|
||||
|
||||
onionshare-cli --website --public \
|
||||
--persistent "$host_dir/.onionshare-session" \
|
||||
-v \
|
||||
"$host_dir" >"$LOG_DIR/onionshare.log" 2>&1 &
|
||||
OS_PID=$!
|
||||
|
||||
# Wait for URL
|
||||
# Wait for URL with timeout and feedback
|
||||
echo "Waiting for OnionShare to generate URL (up to 120s)..."
|
||||
URL=""
|
||||
i=0
|
||||
while [ $i -lt 90 ]; do
|
||||
if grep -Eo 'http[^ ]+\.onion' "$LOG_DIR/onionshare.log" | head -n1 >"$LOG_DIR/url.txt"; then
|
||||
URL=$(cat "$LOG_DIR/url.txt")
|
||||
[ -n "$URL" ] && break
|
||||
for i in {1..120}; do
|
||||
# Use -a to process as text even if there are binary chars (ANSI codes)
|
||||
if grep -aEo 'http[s]?://[a-z2-7]{56}\.onion' "$LOG_DIR/onionshare.log" | head -n1 >"$LOG_DIR/url.txt"; then
|
||||
URL=$(cat "$LOG_DIR/url.txt" | tr -d '\r\n ')
|
||||
if [ -n "$URL" ]; then
|
||||
echo "Successfully generated Onion URL: $URL"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
i=$((i + 1))
|
||||
[ $((i % 10)) -eq 0 ] && echo "... still waiting ($i s) ..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ -z "$URL" ]; then
|
||||
echo "Onion URL not found" >&2
|
||||
echo "CRITICAL: Onion URL not found after 120s" >&2
|
||||
echo "--- Last 20 lines of OnionShare log ---"
|
||||
tail -n 20 "$LOG_DIR/onionshare.log"
|
||||
kill $OS_PID || true
|
||||
kill $TOR_PID || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify URL in config
|
||||
grep -q "$URL" "$host_dir/config.json"
|
||||
if ! grep -q "$URL" "$host_dir/config.json"; then
|
||||
echo "CRITICAL: URL $URL not found in $host_dir/config.json" >&2
|
||||
cat "$host_dir/config.json"
|
||||
kill $OS_PID || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fetch page via Tor
|
||||
curl --socks5-hostname 127.0.0.1:9050 "$URL" | grep -q '<title>'
|
||||
# Fetch page via Tor (retry a few times as circuits can be flaky)
|
||||
echo "Verifying reachability via Tor (on port 9050)..."
|
||||
FETCHED=0
|
||||
for attempt in {1..5}; do
|
||||
echo "Attempt $attempt..."
|
||||
if curl --socks5-hostname 127.0.0.1:9050 -fsSL "$URL" | grep -q '<title>'; then
|
||||
echo "Success: Site is reachable via Tor."
|
||||
FETCHED=1
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ $FETCHED -eq 0 ]; then
|
||||
echo "WARNING: Site could not be reached via Tor after 5 attempts, but OnionShare is running."
|
||||
# In CI, we might skip hard failure if Tor network is exceptionally slow/blocked
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
kill $OS_PID
|
||||
kill $TOR_PID
|
||||
echo "Cleaning up..."
|
||||
kill $OS_PID || true
|
||||
kill ${TOR_PID:-} || true
|
||||
wait $OS_PID 2>/dev/null || true
|
||||
wait $TOR_PID 2>/dev/null || true
|
||||
else
|
||||
echo "Skipping network-dependent tests" >&2
|
||||
exit 0
|
||||
echo "Skipping network-dependent tests (VOXVERA_E2E_OFFLINE=1)" >&2
|
||||
fi
|
||||
|
||||
echo "E2E verification complete at $(date)"
|
||||
exit 0
|
||||
+49
-31
@@ -115,47 +115,65 @@ download_binary() {
|
||||
}
|
||||
|
||||
# Install VoxVera (Always use --force to ensure fresh installation)
|
||||
if command_exists curl; then
|
||||
msg "Attempting to download latest VoxVera binary..."
|
||||
# Get the latest release tag
|
||||
latest=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/PR0M3TH3AN/VoxVera/releases/latest | grep -oE '[^/]+$')
|
||||
|
||||
install_dir="$HOME/.local/bin"
|
||||
mkdir -p "$install_dir"
|
||||
dest="$install_dir/voxvera"
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
ARCH=$(uname -m)
|
||||
|
||||
case "$OS" in
|
||||
linux*) BINARY="voxvera-linux" ;;
|
||||
darwin*) BINARY="voxvera-macos" ;;
|
||||
*) BINARY="voxvera-linux" ;;
|
||||
esac
|
||||
|
||||
# Try to download architecture-specific binary (e.g. voxvera-linux-x86_64)
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/download/${latest}/${BINARY}-${ARCH}"
|
||||
# For Windows, PyInstaller might add .exe but here we focus on linux/mac
|
||||
|
||||
msg "Downloading from: $url"
|
||||
status=$(curl -w "%{http_code}" -fsSL "$url" -o "$dest" || echo "failed")
|
||||
|
||||
if [ "$status" = "200" ]; then
|
||||
chmod +x "$dest"
|
||||
msg "VoxVera binary (${ARCH}) installed to $dest"
|
||||
[[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && warn "Add \$HOME/.local/bin to your PATH."
|
||||
exit 0
|
||||
else
|
||||
warn "Binary download failed (status: $status). Falling back to source installation."
|
||||
[ -f "$dest" ] && rm "$dest"
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists pipx; then
|
||||
msg "Installing/Re-installing VoxVera via pipx..."
|
||||
msg "Falling back to pipx installation (this may take a few minutes)..."
|
||||
if pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
|
||||
pipx ensurepath --force
|
||||
msg "\nVoxVera installed/updated successfully via pipx."
|
||||
msg "IMPORTANT: Please restart your terminal or run 'source ~/.bashrc' (or your shell config) to use 'voxvera'."
|
||||
exit 0
|
||||
fi
|
||||
warn "pipx install failed, trying binary fallback..."
|
||||
warn "pipx install failed, trying pip fallback..."
|
||||
fi
|
||||
|
||||
install_dir="$HOME/.local/bin"
|
||||
mkdir -p "$install_dir"
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
case "$OS" in
|
||||
linux*) BINARY="voxvera-linux" ;;
|
||||
darwin*) BINARY="voxvera-macos" ;;
|
||||
*) BINARY="voxvera-linux" ;;
|
||||
esac
|
||||
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/${BINARY}"
|
||||
dest="$install_dir/voxvera"
|
||||
msg "Downloading VoxVera binary from $url..."
|
||||
if download_binary "$url" "$dest"; then
|
||||
msg "VoxVera binary downloaded to $dest"
|
||||
[[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && warn "Add \$HOME/.local/bin to your PATH."
|
||||
# Fallback to pip
|
||||
msg "Installing/Updating VoxVera via pip..."
|
||||
PIP_OPTS="--user --upgrade"
|
||||
if [ -f /etc/debian_version ]; then
|
||||
DEB_VER=$(cat /etc/debian_version | cut -d. -f1)
|
||||
if [[ "$DEB_VER" =~ ^[0-9]+$ ]] && [ "$DEB_VER" -ge 12 ]; then
|
||||
PIP_OPTS="$PIP_OPTS --break-system-packages"
|
||||
fi
|
||||
fi
|
||||
if pip install $PIP_OPTS 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
|
||||
msg "VoxVera installed via pip."
|
||||
else
|
||||
warn "Binary download failed. Attempting pip install as last resort..."
|
||||
# Use --break-system-packages on Debian 12+ if necessary
|
||||
PIP_OPTS="--user --upgrade"
|
||||
if [ -f /etc/debian_version ]; then
|
||||
DEB_VER=$(cat /etc/debian_version | cut -d. -f1)
|
||||
if [[ "$DEB_VER" =~ ^[0-9]+$ ]] && [ "$DEB_VER" -ge 12 ]; then
|
||||
PIP_OPTS="$PIP_OPTS --break-system-packages"
|
||||
fi
|
||||
fi
|
||||
if pip install $PIP_OPTS 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
|
||||
msg "VoxVera installed via pip."
|
||||
else
|
||||
die "Installation failed."
|
||||
fi
|
||||
die "Installation failed."
|
||||
fi
|
||||
|
||||
msg "\nVoxVera installed/updated successfully. Run 'voxvera check' to verify."
|
||||
@@ -5,7 +5,7 @@ set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
ARCH=$(uname -m)
|
||||
BINARY="voxvera/resources/bin/voxvera-linux-$ARCH"
|
||||
BINARY=${1:-"voxvera/resources/bin/voxvera-linux-$ARCH"}
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY. Run scripts/build_binaries.sh first." >&2
|
||||
|
||||
@@ -5,7 +5,7 @@ set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
ARCH=$(uname -m)
|
||||
BINARY="voxvera/resources/bin/voxvera-linux-$ARCH"
|
||||
BINARY=${1:-"voxvera/resources/bin/voxvera-linux-$ARCH"}
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY. Run scripts/build_binaries.sh first." >&2
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -378,7 +378,7 @@
|
||||
</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": "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
|
||||
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.", "name": "Vox Vera Printable Flyers"}}, "de": {"meta": {"language_name": "Deutsch", "flag": "\ud83c\udde9\ud83c\uddea"}, "web": {"teLine truncated
|
||||
|
||||
function t(path, lang) {
|
||||
const parts = path.split('.');
|
||||
|
||||
+17
-1
@@ -15,6 +15,7 @@ Requirements:
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
@@ -107,10 +108,12 @@ class TestFullWorkflow:
|
||||
# Setup temp workspace
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
shutil.copytree(repo_root / "voxvera" / "src", tmp_path / "src")
|
||||
shutil.copytree(repo_root / "voxvera" / "locales", tmp_path / "locales")
|
||||
|
||||
# Monkeypatch ROOT to use temp directory
|
||||
monkeypatch.setattr(cli, "ROOT", tmp_path)
|
||||
monkeypatch.setattr(cli, "_src_res", lambda *p: tmp_path / "src" / Path(*p))
|
||||
monkeypatch.setattr(cli, "_locale_res", lambda *p: tmp_path / "locales" / Path(*p))
|
||||
|
||||
# Create test config
|
||||
config = {
|
||||
@@ -201,8 +204,11 @@ class TestFullWorkflow:
|
||||
# Setup
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
shutil.copytree(repo_root / "voxvera" / "src", tmp_path / "src")
|
||||
shutil.copytree(repo_root / "voxvera" / "locales", tmp_path / "locales")
|
||||
|
||||
monkeypatch.setattr(cli, "ROOT", tmp_path)
|
||||
monkeypatch.setattr(cli, "_src_res", lambda *p: tmp_path / "src" / Path(*p))
|
||||
monkeypatch.setattr(cli, "_locale_res", lambda *p: tmp_path / "locales" / Path(*p))
|
||||
|
||||
config = {
|
||||
"name": "Reachability Test",
|
||||
@@ -230,6 +236,13 @@ class TestFullWorkflow:
|
||||
logfile = host_dir / "onionshare.log"
|
||||
log_fh = open(logfile, "w")
|
||||
|
||||
# Pass Tor environment variables to ensure it uses the running Tor
|
||||
env = os.environ.copy()
|
||||
tor_port = check_tor_running()
|
||||
if tor_port:
|
||||
env["TOR_SOCKS_PORT"] = str(tor_port)
|
||||
env["TOR_CONTROL_PORT"] = str(tor_port + 1)
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-m", "voxvera.cli",
|
||||
@@ -241,7 +254,7 @@ class TestFullWorkflow:
|
||||
"-v",
|
||||
str(host_dir),
|
||||
]
|
||||
proc = subprocess.Popen(cmd, stdout=log_fh, stderr=subprocess.STDOUT)
|
||||
proc = subprocess.Popen(cmd, stdout=log_fh, stderr=subprocess.STDOUT, env=env)
|
||||
|
||||
try:
|
||||
# Wait for onion URL to appear in the log
|
||||
@@ -321,8 +334,11 @@ class TestSiteFiles:
|
||||
# Setup
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
shutil.copytree(repo_root / "voxvera" / "src", tmp_path / "src")
|
||||
shutil.copytree(repo_root / "voxvera" / "locales", tmp_path / "locales")
|
||||
|
||||
monkeypatch.setattr(cli, "ROOT", tmp_path)
|
||||
monkeypatch.setattr(cli, "_src_res", lambda *p: tmp_path / "src" / Path(*p))
|
||||
monkeypatch.setattr(cli, "_locale_res", lambda *p: tmp_path / "locales" / Path(*p))
|
||||
|
||||
config = {
|
||||
"name": "Test",
|
||||
|
||||
+39
-18
@@ -64,31 +64,52 @@ elif ! command_exists onionshare-cli && ! command_exists onionshare; then
|
||||
fi
|
||||
fi
|
||||
|
||||
## -------- install VoxVera (prefers pipx, falls back) ---------------
|
||||
## -------- install VoxVera (prefers binary, falls back) -------------
|
||||
install_voxvera() {
|
||||
local install_dir="$HOME/.local/bin"
|
||||
mkdir -p "$install_dir"
|
||||
local url latest dest="$install_dir/voxvera"
|
||||
|
||||
if command_exists curl; then
|
||||
msg "Attempting to download latest VoxVera binary..."
|
||||
# Get the latest release tag
|
||||
latest=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/PR0M3TH3AN/VoxVera/releases/latest | grep -oE '[^/]+$')
|
||||
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
ARCH=$(uname -m)
|
||||
case "$OS" in
|
||||
linux*) BINARY="voxvera-linux" ;;
|
||||
darwin*) BINARY="voxvera-macos" ;;
|
||||
*) BINARY="voxvera-linux" ;;
|
||||
esac
|
||||
|
||||
# Try to download architecture-specific binary (e.g. voxvera-linux-x86_64)
|
||||
url="https://github.com/PR0M3TH3AN/VoxVera/releases/download/${latest}/${BINARY}-${ARCH}"
|
||||
|
||||
msg "Downloading from: $url"
|
||||
local status
|
||||
status=$(curl -w "%{http_code}" -fsSL "$url" -o "$dest" || echo "failed")
|
||||
|
||||
if [ "$status" = "200" ]; then
|
||||
chmod +x "$dest"
|
||||
msg "VoxVera binary (${ARCH}) installed to $dest"
|
||||
return 0
|
||||
else
|
||||
warn "Binary download failed (status: $status). Falling back to source installation."
|
||||
[ -f "$dest" ] && rm "$dest"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback to pipx
|
||||
if command_exists pipx; then
|
||||
msg "Installing/Re-installing VoxVera via pipx..."
|
||||
msg "Falling back to pipx installation (this may take a few minutes)..."
|
||||
if pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
|
||||
pipx ensurepath --force
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
# fallback: binary
|
||||
local install_dir="$HOME/.local/bin"
|
||||
mkdir -p "$install_dir"
|
||||
local url latest dest="$install_dir/voxvera"
|
||||
if command_exists curl; then
|
||||
msg "Downloading VoxVera binary..."
|
||||
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-linux"
|
||||
if curl -fsSL "$url" -o "$dest"; then
|
||||
chmod +x "$dest"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
# fallback: pip
|
||||
|
||||
# Fallback to pip
|
||||
msg "Installing/Updating VoxVera via pip..."
|
||||
PIP_OPTS="--user --upgrade"
|
||||
if [ -f /etc/debian_version ]; then
|
||||
|
||||
+47
-4
@@ -12,6 +12,7 @@ from importlib import resources
|
||||
from importlib.resources.abc import Traversable
|
||||
from InquirerPy import prompt, inquirer
|
||||
from rich.console import Console
|
||||
from voxvera import __version__
|
||||
|
||||
# package root (contains bundled templates and src/)
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
@@ -460,6 +461,14 @@ def build_assets(
|
||||
# Bundle locales for the web engine
|
||||
all_locales = {}
|
||||
locale_files = sorted(glob.glob(str(ROOT / "locales" / "*.json")))
|
||||
|
||||
# Load master defaults from src/config.json
|
||||
src_config_path = ROOT / "src" / "config.json"
|
||||
master_defaults = {}
|
||||
if src_config_path.exists():
|
||||
with open(src_config_path, "r", encoding="utf-8") as sf:
|
||||
master_defaults = json.load(sf)
|
||||
|
||||
for lp in locale_files:
|
||||
code = Path(lp).stem
|
||||
with open(lp, "r", encoding="utf-8") as lf:
|
||||
@@ -470,10 +479,12 @@ def build_assets(
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
|
||||
# Override landing data with user custom config data so JS doesn't overwrite it
|
||||
# Override landing data with user custom config data if it differs from master defaults
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
if field in data and data[field]:
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
# Only override if the value is different from the master default
|
||||
if data[field] != master_defaults.get(field):
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
|
||||
html = html.replace("{{locales}}", json.dumps(all_locales))
|
||||
|
||||
@@ -496,6 +507,13 @@ def build_assets(
|
||||
for field in landing_fields:
|
||||
# Use data from config if available, otherwise use localized default
|
||||
value = data.get(field, landing_defaults.get(field, ""))
|
||||
|
||||
# If the value from config is just the master default,
|
||||
# and we are NOT in English, use the localized default instead.
|
||||
if data.get("lang", "en") != "en":
|
||||
if value == master_defaults.get(field):
|
||||
value = landing_defaults.get(field, value)
|
||||
|
||||
# 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)
|
||||
@@ -601,6 +619,8 @@ def _internal_onionshare():
|
||||
bundled_tor = tor_dir / ("tor.exe" if "windows" in system else "tor")
|
||||
if bundled_tor.exists() and bundled_tor.stat().st_size > 100:
|
||||
os.environ["PATH"] = str(tor_dir) + os.pathsep + os.environ.get("PATH", "")
|
||||
# Robustness: explicitly tell OnionShare where Tor is
|
||||
os.environ["TOR_BINARY"] = str(bundled_tor)
|
||||
|
||||
try:
|
||||
from onionshare_cli import main
|
||||
@@ -919,6 +939,14 @@ def build_site():
|
||||
# Bundle locales (Include landing tokens for the main site)
|
||||
all_locales = {}
|
||||
locale_files = sorted(glob.glob(str(ROOT / "locales" / "*.json")))
|
||||
|
||||
# Load master defaults from src/config.json
|
||||
src_config_path = ROOT / "src" / "config.json"
|
||||
master_defaults = {}
|
||||
if src_config_path.exists():
|
||||
with open(src_config_path, "r", encoding="utf-8") as sf:
|
||||
master_defaults = json.load(sf)
|
||||
|
||||
for lp in locale_files:
|
||||
code = Path(lp).stem
|
||||
with open(lp, "r", encoding="utf-8") as lf:
|
||||
@@ -929,10 +957,12 @@ def build_site():
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
|
||||
# Override landing data with user custom config data so JS doesn't overwrite it
|
||||
# Override landing data with user custom config data if it differs from master defaults
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
if field in data and data[field]:
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
# Only override if the value is different from the master default
|
||||
if data[field] != master_defaults.get(field):
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
|
||||
html = html.replace("{{locales}}", json.dumps(all_locales))
|
||||
|
||||
@@ -954,6 +984,13 @@ def build_site():
|
||||
for field in landing_fields:
|
||||
# Use data from config if available, otherwise use localized default
|
||||
value = data.get(field, landing_defaults.get(field, ""))
|
||||
|
||||
# If the value from config is just the master default,
|
||||
# and we are NOT in English, use the localized default instead.
|
||||
if current_lang != "en":
|
||||
if value == master_defaults.get(field):
|
||||
value = landing_defaults.get(field, value)
|
||||
|
||||
# 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)
|
||||
@@ -1272,6 +1309,12 @@ def main(argv=None):
|
||||
"--lang", "--language", "--sprache", "--idioma",
|
||||
help="Force language code (e.g. en, es, de)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version", "-V",
|
||||
action="version",
|
||||
version=f"VoxVera {__version__}",
|
||||
help="Show version information and exit",
|
||||
)
|
||||
sub = parser.add_subparsers(dest="command")
|
||||
|
||||
sub.add_parser("lang", help="Change language preference")
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"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.",
|
||||
"url": "https://voxvera.org/",
|
||||
"tear_off_link": "http://5gwnlvdopooskthgkvgiwdrcmdgd5uv7yrkajpwa6jdrz6oxnhyjdeqd.onion",
|
||||
"url_message": "Get VoxVera and start spreading your message.",
|
||||
"url": "https://github.com/PR0M3TH3AN/VoxVera",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user