Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3fc4ea0
Threads and waitress instead of gevent (M9)
claude Oct 15, 2026
4b8729f
A button press is acknowledged where it arrives, not by the worker
claude Oct 15, 2026
bb303d3
Telegram long polling: commands with no server (pull mode)
claude Oct 15, 2026
6afa9bb
Slack Socket Mode: commands with no server (pull mode)
claude Oct 15, 2026
86346c8
The Discord bot, over the gateway or the interactions endpoint
claude Oct 15, 2026
dc54f31
Incoming webhooks out: every network posts as its bot, buttons and all
claude Oct 15, 2026
f3a2cb1
Scheduled reports run in the bot's process, not child processes
claude Oct 15, 2026
2c595ad
Jobs per chat, managed with .jobs; schedule.cron migrated once
claude Oct 15, 2026
a0cb7a4
A chat's scheduled price, shorts or cal job runs as scheduled
claude Oct 15, 2026
d5a1a5e
Watchlist and plans per chat
claude Oct 15, 2026
d5253dd
Docs: every bot listens, on every network
claude Oct 15, 2026
3f9ac25
Remove pandas
claude Oct 15, 2026
993e3fe
Slash commands on every network
claude Oct 15, 2026
68f1113
Discord autocomplete for slash command arguments, in both modes
claude Oct 15, 2026
8336cfc
Docker: a Dockerfile and a compose file, as a second way to install
claude Oct 15, 2026
aebe38c
Docs: a short README, and a file per topic in doc/
claude Oct 15, 2026
6b8fe3c
Split the protocol code per network: common, webhook, and the persist…
claude Oct 15, 2026
834ea8b
Follow the current API docs: link previews, typing, User-Agent, webho…
claude Oct 15, 2026
750ecf0
Shorter startup log lines: the keys set, and the preferences file
claude Oct 15, 2026
478bea9
Shorter startup log line without preferences.conf: "reload unavailable"
claude Oct 15, 2026
cc5b7ff
The preferences template sets nothing: every key commented out, never…
claude Oct 15, 2026
9015d7a
Fix .earnings on ETFs, and keep log lines whole when threads log at once
claude Oct 16, 2026
0296438
Log levels, syslog style: log_level replaces debug; each request logg…
claude Oct 16, 2026
8ca6335
util/flush.py flushes the cache by age, or all of it; finbot-flush.se…
claude Oct 16, 2026
dc8cb2a
Cache misses log at DEBUG, with hits and writes
claude Oct 16, 2026
942eb60
Request log lines are cut at 73 bytes
claude Oct 16, 2026
aa13089
Every row in the bar chart, shorter names: no fund-trust boilerplate,…
claude Oct 16, 2026
f11efc0
A US listing keeps its own dates; "no market cap" logs at DEBUG
claude Oct 16, 2026
94095b1
The same message sent again within a second is taken once
claude Oct 16, 2026
db29d2b
No home-market suffix for a US listing: no more ARM.L or CLBT.TA
claude Oct 16, 2026
a5cee80
Merge main into claude/kind-newton-ad0z2q
claude Oct 16, 2026
11e4f76
Request log lines: "POST from telegram: we answered 200 OK"
claude Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# what the image does without: history, tools for development, the tests and their samples, the docs
.git
.github
.claude
.venv
var
tests
doc
img
**/__pycache__
*.pyc
84 changes: 82 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,93 @@ jobs:
cache: pip
- if: steps.changes.outputs.docs_only != 'true'
run: |
pip install -r requirements.txt
pip install -r requirements.txt -r requirements-optional.txt
python -m compileall -q *.py lib util
python -m unittest discover -s tests -v -b

docker:
# The image built as a Docker host builds it, from Debian's packages: a package renamed or gone fails here before
# production meets it. Then every module imports in it, and the container starts as compose.yaml starts it.
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the base branch is needed to see what a pull request changes
- id: changes
name: Classify the change
# As in the test job: a pull request that only touches docs skips the steps below.
env:
BASE_REF: ${{ github.base_ref }}
run: |
docs_only=false
if [ "$GITHUB_EVENT_NAME" = pull_request ]; then
files=$(git -c core.quotePath=false diff --name-only --no-renames "origin/$BASE_REF...HEAD")
docs='^(README\.md|CLAUDE\.md|doc/[^/]+\.md|img/[^/]+\.png)$'
if [ -n "$files" ] && ! grep -qvE "$docs" <<< "$files"; then
docs_only=true
fi
fi
echo "docs_only=$docs_only" >> "$GITHUB_OUTPUT"
- if: steps.changes.outputs.docs_only != 'true'
name: Build the image
run: docker build --build-arg GIT_COMMIT="$GITHUB_SHA" -t finbot .
- if: steps.changes.outputs.docs_only != 'true'
name: Every module imports in the image, the optional libraries too, and the log names the commit
run: |
docker run --rm -i --read-only --tmpfs /tmp -e DATA_DIR=/tmp/data finbot python3 - <<'EOF'
import importlib, os, pkgutil, bot, lib, lib.reports
for package in (lib, lib.reports):
for module in pkgutil.iter_modules(package.__path__, package.__name__ + '.'):
importlib.import_module(module.name)
import discord, nacl, waitress, websocket
assert bot.git_version() == os.environ['FINBOT_COMMIT'][:7], bot.git_version()
print('every module imports; finbot', bot.git_version())
EOF
- if: steps.changes.outputs.docs_only != 'true'
name: The container starts as compose starts it, copies the defaults, reloads and flushes
run: |
data="$RUNNER_TEMP/finbot-data"
mkdir "$data" && sudo chown 10001:10001 "$data"
docker run -d --name finbot --init --read-only --tmpfs /tmp --cap-drop ALL \
--security-opt no-new-privileges:true --pids-limit 256 --memory 400m --memory-swap 400m \
-e DATA_DIR=/data -v "$data:/data" -v "$PWD/defaults/finbot.env:/run/finbot.env:ro" finbot
wait_for() { # the log saved first: grep -q would cut docker logs off mid-write, which pipefail counts
for _ in $(seq 60); do
docker logs finbot > "$RUNNER_TEMP/log" 2>&1
grep -q "$1" "$RUNNER_TEMP/log" && return 0
sleep 1
done
cat "$RUNNER_TEMP/log"
echo "::error::never logged: $1"
return 1
}
wait_for 'finbot is running'
test -f "$data/preferences.conf"
docker kill -s HUP finbot
wait_for 'config: reloaded'
docker kill -s USR1 finbot
wait_for 'cache: flushed'
docker rm -f finbot
- if: steps.changes.outputs.docs_only != 'true'
name: A mistake in the startup file stops the container before the bot starts
run: |
printf 'telegramBotTokn=x\n' > "$RUNNER_TEMP/bad.env"
if docker run --rm --read-only --tmpfs /tmp -e DATA_DIR=/tmp \
-v "$RUNNER_TEMP/bad.env:/run/finbot.env:ro" finbot > "$RUNNER_TEMP/out" 2>&1; then
cat "$RUNNER_TEMP/out"
echo "::error::the container started with a mistake in its startup file"
exit 1
fi
cat "$RUNNER_TEMP/out"
grep -q 'unknown key telegramBotTokn' "$RUNNER_TEMP/out"
if grep -q 'finbot is running' "$RUNNER_TEMP/out"; then
echo "::error::the bot started with a mistake in its startup file"
exit 1
fi

notify:
# Telegram alert when the weekly run fails. Needs repository secrets TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID.
needs: test
needs: [test, docker]
if: failure() && github.event_name == 'schedule'
runs-on: ubuntu-24.04
steps:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.*
!.gitignore
!.editorconfig
!.dockerignore
!.python-version
!.claude
!.github
Expand Down
Loading
Loading