From e76d68814ffd57ed5faaee7e0a581dd0e5cc55c8 Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:05:15 +0800 Subject: [PATCH] feat(app): add This Week in Past --- apps/this-week-in-past/1.32.41/.env.sample | 6 ++ apps/this-week-in-past/1.32.41/data.yml | 88 ++++++++++++++++++ .../1.32.41/docker-compose.yml | 24 +++++ .../this-week-in-past/1.32.41/scripts/init.sh | 64 +++++++++++++ .../1.32.41/scripts/uninstall.sh | 2 + .../1.32.41/scripts/upgrade.sh | 2 + apps/this-week-in-past/README.md | 56 +++++++++++ apps/this-week-in-past/data.yml | 33 +++++++ apps/this-week-in-past/latest/.env.sample | 6 ++ apps/this-week-in-past/latest/data.yml | 88 ++++++++++++++++++ .../latest/docker-compose.yml | 24 +++++ apps/this-week-in-past/latest/scripts/init.sh | 64 +++++++++++++ .../latest/scripts/uninstall.sh | 2 + .../latest/scripts/upgrade.sh | 2 + apps/this-week-in-past/logo.png | Bin 0 -> 4434 bytes 15 files changed, 461 insertions(+) create mode 100644 apps/this-week-in-past/1.32.41/.env.sample create mode 100644 apps/this-week-in-past/1.32.41/data.yml create mode 100644 apps/this-week-in-past/1.32.41/docker-compose.yml create mode 100755 apps/this-week-in-past/1.32.41/scripts/init.sh create mode 100755 apps/this-week-in-past/1.32.41/scripts/uninstall.sh create mode 100755 apps/this-week-in-past/1.32.41/scripts/upgrade.sh create mode 100644 apps/this-week-in-past/README.md create mode 100644 apps/this-week-in-past/data.yml create mode 100644 apps/this-week-in-past/latest/.env.sample create mode 100644 apps/this-week-in-past/latest/data.yml create mode 100644 apps/this-week-in-past/latest/docker-compose.yml create mode 100755 apps/this-week-in-past/latest/scripts/init.sh create mode 100755 apps/this-week-in-past/latest/scripts/uninstall.sh create mode 100755 apps/this-week-in-past/latest/scripts/upgrade.sh create mode 100644 apps/this-week-in-past/logo.png diff --git a/apps/this-week-in-past/1.32.41/.env.sample b/apps/this-week-in-past/1.32.41/.env.sample new file mode 100644 index 000000000..508d372f7 --- /dev/null +++ b/apps/this-week-in-past/1.32.41/.env.sample @@ -0,0 +1,6 @@ +PANEL_APP_PORT_HTTP=40829 +PHOTO_LIBRARY_PATH=./data/photos +APP_DATA_DIR=./data/app +SLIDESHOW_INTERVAL=30 +RANDOM_SLIDESHOW=false +CONTAINER_NAME=this-week-in-past diff --git a/apps/this-week-in-past/1.32.41/data.yml b/apps/this-week-in-past/1.32.41/data.yml new file mode 100644 index 000000000..8ef51797b --- /dev/null +++ b/apps/this-week-in-past/1.32.41/data.yml @@ -0,0 +1,88 @@ +additionalProperties: + formFields: + - default: 40829 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: Web 端口 + label: + en: Web Port + zh: Web 端口 + zh-Hant: Web 連接埠 + ja: Web ポート + ko: Web 포트 + ru: Веб-порт + ms: Port Web + pt-br: Porta Web + required: true + rule: paramPort + type: number + - default: ./data/photos + edit: true + envKey: PHOTO_LIBRARY_PATH + labelEn: Photo Library Directory + labelZh: 照片库目录 + label: + en: Photo Library Directory + zh: 照片库目录 + zh-Hant: 相片庫目錄 + ja: 写真ライブラリーディレクトリ + ko: 사진 라이브러리 디렉터리 + ru: Каталог фототеки + ms: Direktori Pustaka Foto + pt-br: Diretorio da biblioteca de fotos + required: true + type: text + - default: ./data/app + disabled: true + envKey: APP_DATA_DIR + labelEn: Application Data Directory + labelZh: 应用数据目录 + label: + en: Application Data Directory + zh: 应用数据目录 + zh-Hant: 應用程式資料目錄 + ja: アプリケーションデータディレクトリ + ko: 애플리케이션 데이터 디렉터리 + ru: Каталог данных приложения + ms: Direktori Data Aplikasi + pt-br: Diretorio de dados do aplicativo + required: true + type: text + - default: 30 + edit: true + envKey: SLIDESHOW_INTERVAL + labelEn: Slideshow Interval (seconds) + labelZh: 幻灯片间隔(秒) + label: + en: Slideshow Interval (seconds) + zh: 幻灯片间隔(秒) + zh-Hant: 幻燈片間隔(秒) + ja: スライドショー間隔(秒) + ko: 슬라이드쇼 간격(초) + ru: Интервал слайд-шоу (секунды) + ms: Selang Tayangan Slaid (saat) + pt-br: Intervalo da apresentacao (segundos) + required: true + type: number + - default: "false" + edit: true + envKey: RANDOM_SLIDESHOW + labelEn: Random Slideshow + labelZh: 随机幻灯片 + label: + en: Random Slideshow + zh: 随机幻灯片 + zh-Hant: 隨機幻燈片 + ja: ランダムスライドショー + ko: 무작위 슬라이드쇼 + ru: Случайное слайд-шоу + ms: Tayangan Slaid Rawak + pt-br: Apresentacao aleatoria + required: true + type: select + values: + - label: "false" + value: "false" + - label: "true" + value: "true" diff --git a/apps/this-week-in-past/1.32.41/docker-compose.yml b/apps/this-week-in-past/1.32.41/docker-compose.yml new file mode 100644 index 000000000..86cc9a2c5 --- /dev/null +++ b/apps/this-week-in-past/1.32.41/docker-compose.yml @@ -0,0 +1,24 @@ +services: + this-week-in-past: + image: "rouhim/this-week-in-past:1.32.41" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + environment: + - RESOURCE_PATHS=/resources + - DATA_FOLDER=/data + - PORT=8080 + - SLIDESHOW_INTERVAL=${SLIDESHOW_INTERVAL} + - RANDOM_SLIDESHOW=${RANDOM_SLIDESHOW} + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + volumes: + - "${PHOTO_LIBRARY_PATH}:/resources:ro" + - "${APP_DATA_DIR}:/data" + labels: + createdBy: "Apps" + networks: + - 1panel-network + +networks: + 1panel-network: + external: true diff --git a/apps/this-week-in-past/1.32.41/scripts/init.sh b/apps/this-week-in-past/1.32.41/scripts/init.sh new file mode 100755 index 000000000..aa17d67d3 --- /dev/null +++ b/apps/this-week-in-past/1.32.41/scripts/init.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +read_env_value() { + local key="$1" + local value + [[ -f "$ENV_FILE" ]] || return 0 + value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)" + case "$value" in + \"*\") value="${value#\"}"; value="${value%\"}" ;; + \'*\') value="${value#\'}"; value="${value%\'}" ;; + esac + printf '%s\n' "$value" +} + +configured_value() { + local key="$1" + local default_value="$2" + local value="${!key:-}" + if [[ -z "$value" ]]; then + value="$(read_env_value "$key")" + fi + printf '%s\n' "${value:-$default_value}" +} + +photo_path="$(configured_value PHOTO_LIBRARY_PATH ./data/photos)" +[[ -n "$photo_path" ]] || fail "PHOTO_LIBRARY_PATH must not be empty" +if [[ "$photo_path" = /* ]]; then + resolved_photo_path="$(realpath -m -- "$photo_path")" +else + resolved_photo_path="$(realpath -m -- "$ROOT_DIR/${photo_path#./}")" + case "$resolved_photo_path" in + "$ROOT_DIR"/*) ;; + *) fail "relative PHOTO_LIBRARY_PATH must remain inside the application version directory" ;; + esac +fi +mkdir -p -- "$resolved_photo_path" +[[ -d "$resolved_photo_path" ]] || fail "PHOTO_LIBRARY_PATH must resolve to a directory" + +data_path="$(configured_value APP_DATA_DIR ./data/app)" +[[ -n "$data_path" ]] || fail "APP_DATA_DIR must not be empty" +[[ "$data_path" != /* ]] || fail "APP_DATA_DIR must be relative to the application version directory" +resolved_data_path="$(realpath -m -- "$ROOT_DIR/${data_path#./}")" +case "$resolved_data_path" in + "$ROOT_DIR"/*) ;; + *) fail "APP_DATA_DIR must remain inside the application version directory" ;; +esac +[[ ! -L "$resolved_data_path" ]] || fail "APP_DATA_DIR must not be a symbolic link" +install -d -m 0750 -- "$resolved_data_path" +resolved_data_path="$(realpath -e -- "$resolved_data_path")" +case "$resolved_data_path" in + "$ROOT_DIR"/*) ;; + *) fail "APP_DATA_DIR resolves outside the application version directory" ;; +esac +chown -R --no-dereference 1337:1337 "$resolved_data_path" +chmod 0750 "$resolved_data_path" diff --git a/apps/this-week-in-past/1.32.41/scripts/uninstall.sh b/apps/this-week-in-past/1.32.41/scripts/uninstall.sh new file mode 100755 index 000000000..6e877466e --- /dev/null +++ b/apps/this-week-in-past/1.32.41/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -euo pipefail diff --git a/apps/this-week-in-past/1.32.41/scripts/upgrade.sh b/apps/this-week-in-past/1.32.41/scripts/upgrade.sh new file mode 100755 index 000000000..6e877466e --- /dev/null +++ b/apps/this-week-in-past/1.32.41/scripts/upgrade.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -euo pipefail diff --git a/apps/this-week-in-past/README.md b/apps/this-week-in-past/README.md new file mode 100644 index 000000000..27f64037f --- /dev/null +++ b/apps/this-week-in-past/README.md @@ -0,0 +1,56 @@ +# This Week in Past + +## 产品介绍 + +This Week in Past 会扫描指定的照片库,按拍摄日期找出往年同一周的照片,并在浏览器中生成自动播放的幻灯片。如果本周没有匹配照片,应用会回退到随机照片。 + +## 主要功能 + +- 根据照片 EXIF 拍摄日期展示往年本周回忆 +- 在没有本周照片时显示随机照片 +- 支持浏览器上一张、暂停/继续和下一张操作 +- 支持隐藏指定照片,并将隐藏状态保存在 SQLite 数据库中 +- 可设置幻灯片播放间隔和随机播放模式 + +## 安装与访问 + +- `PHOTO_LIBRARY_PATH` 指向主机上的照片目录,容器只读挂载该目录。 +- `APP_DATA_DIR` 保存 SQLite 数据库和应用缓存,默认由应用包管理。 +- Web 端口由 `PANEL_APP_PORT_HTTP` 设置,安装后通过 `http://<服务器 IP>:<端口>` 访问。 +- 首次索引时间取决于照片数量和存储性能。 + +## 访问说明 + +安装完成后,使用 `http://<服务器 IP>:` 访问应用。应用没有登录认证;从外部网络访问时,请通过 1Panel 反向代理配置 HTTPS 和访问控制。 + +## 数据与安全 + +- 照片库始终以只读方式挂载,应用不会修改原始照片。 +- `APP_DATA_DIR` 包含隐藏照片列表、索引和缓存,重启或更新时会继续使用。 +- 应用本身不提供登录认证。请勿直接暴露到不可信网络;远程访问时应通过 1Panel 反向代理配置 HTTPS 和访问控制。 +- 卸载应用不会删除照片库。迁移或清理前请单独备份应用数据目录。 + +## Introduction + +This Week in Past scans a photo library and builds a browser slideshow from photos taken during the same calendar week in previous years. When no matching photos exist, it falls back to random photos. + +## Features + +- Build weekly memories from photo EXIF capture dates +- Fall back to random photos when the current week has no matches +- Navigate, pause, and resume the slideshow in the browser +- Hide selected photos and persist that state in SQLite +- Configure the slideshow interval and random mode + +## Storage and Security + +- `PHOTO_LIBRARY_PATH` is mounted read-only at `/resources`; original photos are never modified. +- `APP_DATA_DIR` stores the SQLite database and application cache and survives restarts and updates. +- The application does not include authentication. Do not expose it directly to an untrusted network; use a 1Panel reverse proxy with HTTPS and access control for remote access. +- Uninstalling the application does not delete the photo library. + +## Links + +- Project: +- Docker image: +- Frozen Docker documentation: diff --git a/apps/this-week-in-past/data.yml b/apps/this-week-in-past/data.yml new file mode 100644 index 000000000..a189a5d60 --- /dev/null +++ b/apps/this-week-in-past/data.yml @@ -0,0 +1,33 @@ +name: This Week in Past +tags: + - 多媒体 +title: 展示往年本周照片的自托管幻灯片 +description: 展示往年本周照片的自托管幻灯片 +additionalProperties: + key: this-week-in-past + name: This Week in Past + tags: + - Media + shortDescZh: 展示往年本周照片的自托管幻灯片 + shortDescEn: A self-hosted slideshow of photos taken this week in previous years + description: + en: A self-hosted slideshow that finds and displays photos taken during this week in previous years. + zh: 自托管照片幻灯片,用于查找并展示往年同一周拍摄的照片。 + zh-Hant: 自託管相片幻燈片,用於尋找並展示往年同一週拍攝的相片。 + ja: 過去の同じ週に撮影した写真を探して表示するセルフホスト型スライドショーです。 + ko: 지난 여러 해의 같은 주에 촬영한 사진을 찾아 보여 주는 자체 호스팅 슬라이드쇼입니다. + ru: Самостоятельно размещаемое слайд-шоу из фотографий, снятых на этой неделе в прошлые годы. + ms: Tayangan slaid hos sendiri yang memaparkan foto yang diambil pada minggu ini dalam tahun-tahun lalu. + pt-br: Apresentacao de slides auto-hospedada com fotos tiradas nesta semana em anos anteriores. + type: website + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://github.com/RouHim/this-week-in-past + github: https://github.com/RouHim/this-week-in-past + document: https://github.com/RouHim/this-week-in-past/blob/1cbf1e33bc96810d14549daf19e62ac0ecf9eb5d/README.md + architectures: + - amd64 + - arm64 + - armv7 + - armv6 diff --git a/apps/this-week-in-past/latest/.env.sample b/apps/this-week-in-past/latest/.env.sample new file mode 100644 index 000000000..508d372f7 --- /dev/null +++ b/apps/this-week-in-past/latest/.env.sample @@ -0,0 +1,6 @@ +PANEL_APP_PORT_HTTP=40829 +PHOTO_LIBRARY_PATH=./data/photos +APP_DATA_DIR=./data/app +SLIDESHOW_INTERVAL=30 +RANDOM_SLIDESHOW=false +CONTAINER_NAME=this-week-in-past diff --git a/apps/this-week-in-past/latest/data.yml b/apps/this-week-in-past/latest/data.yml new file mode 100644 index 000000000..8ef51797b --- /dev/null +++ b/apps/this-week-in-past/latest/data.yml @@ -0,0 +1,88 @@ +additionalProperties: + formFields: + - default: 40829 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: Web 端口 + label: + en: Web Port + zh: Web 端口 + zh-Hant: Web 連接埠 + ja: Web ポート + ko: Web 포트 + ru: Веб-порт + ms: Port Web + pt-br: Porta Web + required: true + rule: paramPort + type: number + - default: ./data/photos + edit: true + envKey: PHOTO_LIBRARY_PATH + labelEn: Photo Library Directory + labelZh: 照片库目录 + label: + en: Photo Library Directory + zh: 照片库目录 + zh-Hant: 相片庫目錄 + ja: 写真ライブラリーディレクトリ + ko: 사진 라이브러리 디렉터리 + ru: Каталог фототеки + ms: Direktori Pustaka Foto + pt-br: Diretorio da biblioteca de fotos + required: true + type: text + - default: ./data/app + disabled: true + envKey: APP_DATA_DIR + labelEn: Application Data Directory + labelZh: 应用数据目录 + label: + en: Application Data Directory + zh: 应用数据目录 + zh-Hant: 應用程式資料目錄 + ja: アプリケーションデータディレクトリ + ko: 애플리케이션 데이터 디렉터리 + ru: Каталог данных приложения + ms: Direktori Data Aplikasi + pt-br: Diretorio de dados do aplicativo + required: true + type: text + - default: 30 + edit: true + envKey: SLIDESHOW_INTERVAL + labelEn: Slideshow Interval (seconds) + labelZh: 幻灯片间隔(秒) + label: + en: Slideshow Interval (seconds) + zh: 幻灯片间隔(秒) + zh-Hant: 幻燈片間隔(秒) + ja: スライドショー間隔(秒) + ko: 슬라이드쇼 간격(초) + ru: Интервал слайд-шоу (секунды) + ms: Selang Tayangan Slaid (saat) + pt-br: Intervalo da apresentacao (segundos) + required: true + type: number + - default: "false" + edit: true + envKey: RANDOM_SLIDESHOW + labelEn: Random Slideshow + labelZh: 随机幻灯片 + label: + en: Random Slideshow + zh: 随机幻灯片 + zh-Hant: 隨機幻燈片 + ja: ランダムスライドショー + ko: 무작위 슬라이드쇼 + ru: Случайное слайд-шоу + ms: Tayangan Slaid Rawak + pt-br: Apresentacao aleatoria + required: true + type: select + values: + - label: "false" + value: "false" + - label: "true" + value: "true" diff --git a/apps/this-week-in-past/latest/docker-compose.yml b/apps/this-week-in-past/latest/docker-compose.yml new file mode 100644 index 000000000..96a817884 --- /dev/null +++ b/apps/this-week-in-past/latest/docker-compose.yml @@ -0,0 +1,24 @@ +services: + this-week-in-past: + image: "rouhim/this-week-in-past:latest" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + environment: + - RESOURCE_PATHS=/resources + - DATA_FOLDER=/data + - PORT=8080 + - SLIDESHOW_INTERVAL=${SLIDESHOW_INTERVAL} + - RANDOM_SLIDESHOW=${RANDOM_SLIDESHOW} + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + volumes: + - "${PHOTO_LIBRARY_PATH}:/resources:ro" + - "${APP_DATA_DIR}:/data" + labels: + createdBy: "Apps" + networks: + - 1panel-network + +networks: + 1panel-network: + external: true diff --git a/apps/this-week-in-past/latest/scripts/init.sh b/apps/this-week-in-past/latest/scripts/init.sh new file mode 100755 index 000000000..aa17d67d3 --- /dev/null +++ b/apps/this-week-in-past/latest/scripts/init.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +read_env_value() { + local key="$1" + local value + [[ -f "$ENV_FILE" ]] || return 0 + value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)" + case "$value" in + \"*\") value="${value#\"}"; value="${value%\"}" ;; + \'*\') value="${value#\'}"; value="${value%\'}" ;; + esac + printf '%s\n' "$value" +} + +configured_value() { + local key="$1" + local default_value="$2" + local value="${!key:-}" + if [[ -z "$value" ]]; then + value="$(read_env_value "$key")" + fi + printf '%s\n' "${value:-$default_value}" +} + +photo_path="$(configured_value PHOTO_LIBRARY_PATH ./data/photos)" +[[ -n "$photo_path" ]] || fail "PHOTO_LIBRARY_PATH must not be empty" +if [[ "$photo_path" = /* ]]; then + resolved_photo_path="$(realpath -m -- "$photo_path")" +else + resolved_photo_path="$(realpath -m -- "$ROOT_DIR/${photo_path#./}")" + case "$resolved_photo_path" in + "$ROOT_DIR"/*) ;; + *) fail "relative PHOTO_LIBRARY_PATH must remain inside the application version directory" ;; + esac +fi +mkdir -p -- "$resolved_photo_path" +[[ -d "$resolved_photo_path" ]] || fail "PHOTO_LIBRARY_PATH must resolve to a directory" + +data_path="$(configured_value APP_DATA_DIR ./data/app)" +[[ -n "$data_path" ]] || fail "APP_DATA_DIR must not be empty" +[[ "$data_path" != /* ]] || fail "APP_DATA_DIR must be relative to the application version directory" +resolved_data_path="$(realpath -m -- "$ROOT_DIR/${data_path#./}")" +case "$resolved_data_path" in + "$ROOT_DIR"/*) ;; + *) fail "APP_DATA_DIR must remain inside the application version directory" ;; +esac +[[ ! -L "$resolved_data_path" ]] || fail "APP_DATA_DIR must not be a symbolic link" +install -d -m 0750 -- "$resolved_data_path" +resolved_data_path="$(realpath -e -- "$resolved_data_path")" +case "$resolved_data_path" in + "$ROOT_DIR"/*) ;; + *) fail "APP_DATA_DIR resolves outside the application version directory" ;; +esac +chown -R --no-dereference 1337:1337 "$resolved_data_path" +chmod 0750 "$resolved_data_path" diff --git a/apps/this-week-in-past/latest/scripts/uninstall.sh b/apps/this-week-in-past/latest/scripts/uninstall.sh new file mode 100755 index 000000000..6e877466e --- /dev/null +++ b/apps/this-week-in-past/latest/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -euo pipefail diff --git a/apps/this-week-in-past/latest/scripts/upgrade.sh b/apps/this-week-in-past/latest/scripts/upgrade.sh new file mode 100755 index 000000000..6e877466e --- /dev/null +++ b/apps/this-week-in-past/latest/scripts/upgrade.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -euo pipefail diff --git a/apps/this-week-in-past/logo.png b/apps/this-week-in-past/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8b33d77c72b162d11a743e286f4bcb3678ddd8a7 GIT binary patch literal 4434 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 L)m5oQ*hc*iX3AD7 literal 0 HcmV?d00001