nejomibot 0.99.37__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. nejomibot-0.99.37/LICENSE +21 -0
  2. nejomibot-0.99.37/LICENSES/fortune-mod.txt +37 -0
  3. nejomibot-0.99.37/PKG-INFO +504 -0
  4. nejomibot-0.99.37/README.md +473 -0
  5. nejomibot-0.99.37/nejomibot/__init__.py +1 -0
  6. nejomibot-0.99.37/nejomibot/__main__.py +4 -0
  7. nejomibot-0.99.37/nejomibot/access.py +471 -0
  8. nejomibot-0.99.37/nejomibot/account.py +429 -0
  9. nejomibot-0.99.37/nejomibot/account_cli.py +2130 -0
  10. nejomibot-0.99.37/nejomibot/admins.py +290 -0
  11. nejomibot-0.99.37/nejomibot/albums.py +282 -0
  12. nejomibot-0.99.37/nejomibot/allowed.py +282 -0
  13. nejomibot-0.99.37/nejomibot/birthdays.py +530 -0
  14. nejomibot-0.99.37/nejomibot/bot.py +388 -0
  15. nejomibot-0.99.37/nejomibot/chat_cli.py +635 -0
  16. nejomibot-0.99.37/nejomibot/client.py +32 -0
  17. nejomibot-0.99.37/nejomibot/commands.py +601 -0
  18. nejomibot-0.99.37/nejomibot/config.py +427 -0
  19. nejomibot-0.99.37/nejomibot/contacts.py +605 -0
  20. nejomibot-0.99.37/nejomibot/delete.py +238 -0
  21. nejomibot-0.99.37/nejomibot/delivery.py +301 -0
  22. nejomibot-0.99.37/nejomibot/files.py +205 -0
  23. nejomibot-0.99.37/nejomibot/fortune.py +138 -0
  24. nejomibot-0.99.37/nejomibot/fortunes/computers +5733 -0
  25. nejomibot-0.99.37/nejomibot/fortunes/cookie +5698 -0
  26. nejomibot-0.99.37/nejomibot/fortunes/debian +406 -0
  27. nejomibot-0.99.37/nejomibot/fortunes/definitions +5667 -0
  28. nejomibot-0.99.37/nejomibot/fortunes/disclaimer +580 -0
  29. nejomibot-0.99.37/nejomibot/fortunes/drugs +1147 -0
  30. nejomibot-0.99.37/nejomibot/fortunes/education +962 -0
  31. nejomibot-0.99.37/nejomibot/fortunes/ethnic +851 -0
  32. nejomibot-0.99.37/nejomibot/fortunes/food +874 -0
  33. nejomibot-0.99.37/nejomibot/fortunes/fortunes +916 -0
  34. nejomibot-0.99.37/nejomibot/fortunes/fortunes_svenska +3042 -0
  35. nejomibot-0.99.37/nejomibot/fortunes/goedel +200 -0
  36. nejomibot-0.99.37/nejomibot/fortunes/humorists +1049 -0
  37. nejomibot-0.99.37/nejomibot/fortunes/kids +715 -0
  38. nejomibot-0.99.37/nejomibot/fortunes/knghtbrd +2342 -0
  39. nejomibot-0.99.37/nejomibot/fortunes/law +1296 -0
  40. nejomibot-0.99.37/nejomibot/fortunes/linux +1928 -0
  41. nejomibot-0.99.37/nejomibot/fortunes/literature +1329 -0
  42. nejomibot-0.99.37/nejomibot/fortunes/love +574 -0
  43. nejomibot-0.99.37/nejomibot/fortunes/magic +194 -0
  44. nejomibot-0.99.37/nejomibot/fortunes/medicine +423 -0
  45. nejomibot-0.99.37/nejomibot/fortunes/men-women +2548 -0
  46. nejomibot-0.99.37/nejomibot/fortunes/miscellaneous +1750 -0
  47. nejomibot-0.99.37/nejomibot/fortunes/news +257 -0
  48. nejomibot-0.99.37/nejomibot/fortunes/paradoxum +193 -0
  49. nejomibot-0.99.37/nejomibot/fortunes/people +4329 -0
  50. nejomibot-0.99.37/nejomibot/fortunes/perl +1022 -0
  51. nejomibot-0.99.37/nejomibot/fortunes/pets +187 -0
  52. nejomibot-0.99.37/nejomibot/fortunes/platitudes +1366 -0
  53. nejomibot-0.99.37/nejomibot/fortunes/politics +2965 -0
  54. nejomibot-0.99.37/nejomibot/fortunes/pratchett +3429 -0
  55. nejomibot-0.99.37/nejomibot/fortunes/riddles +563 -0
  56. nejomibot-0.99.37/nejomibot/fortunes/rules-of-acquisition +651 -0
  57. nejomibot-0.99.37/nejomibot/fortunes/science +3035 -0
  58. nejomibot-0.99.37/nejomibot/fortunes/shlomif-fav +3592 -0
  59. nejomibot-0.99.37/nejomibot/fortunes/songs-poems +7120 -0
  60. nejomibot-0.99.37/nejomibot/fortunes/sports +799 -0
  61. nejomibot-0.99.37/nejomibot/fortunes/startrek +829 -0
  62. nejomibot-0.99.37/nejomibot/fortunes/tao +1241 -0
  63. nejomibot-0.99.37/nejomibot/fortunes/the-x-files-taglines +82 -0
  64. nejomibot-0.99.37/nejomibot/fortunes/translate-me +61 -0
  65. nejomibot-0.99.37/nejomibot/fortunes/wisdom +1672 -0
  66. nejomibot-0.99.37/nejomibot/fortunes/work +2711 -0
  67. nejomibot-0.99.37/nejomibot/fortunes/zippy +1285 -0
  68. nejomibot-0.99.37/nejomibot/greeting.py +99 -0
  69. nejomibot-0.99.37/nejomibot/guide.py +48 -0
  70. nejomibot-0.99.37/nejomibot/health.py +452 -0
  71. nejomibot-0.99.37/nejomibot/healthcheck.py +42 -0
  72. nejomibot-0.99.37/nejomibot/help.py +682 -0
  73. nejomibot-0.99.37/nejomibot/holidays.py +996 -0
  74. nejomibot-0.99.37/nejomibot/i18n.py +326 -0
  75. nejomibot-0.99.37/nejomibot/incoming.py +291 -0
  76. nejomibot-0.99.37/nejomibot/info.py +141 -0
  77. nejomibot-0.99.37/nejomibot/languages.py +187 -0
  78. nejomibot-0.99.37/nejomibot/limits.py +32 -0
  79. nejomibot-0.99.37/nejomibot/lists.py +169 -0
  80. nejomibot-0.99.37/nejomibot/locale/da.mo +0 -0
  81. nejomibot-0.99.37/nejomibot/locale/da.po +3781 -0
  82. nejomibot-0.99.37/nejomibot/locale/da.toml +85 -0
  83. nejomibot-0.99.37/nejomibot/locale/de.mo +0 -0
  84. nejomibot-0.99.37/nejomibot/locale/de.po +3846 -0
  85. nejomibot-0.99.37/nejomibot/locale/de.toml +84 -0
  86. nejomibot-0.99.37/nejomibot/locale/el.mo +0 -0
  87. nejomibot-0.99.37/nejomibot/locale/el.po +3855 -0
  88. nejomibot-0.99.37/nejomibot/locale/el.toml +86 -0
  89. nejomibot-0.99.37/nejomibot/locale/es.mo +0 -0
  90. nejomibot-0.99.37/nejomibot/locale/es.po +3810 -0
  91. nejomibot-0.99.37/nejomibot/locale/es.toml +85 -0
  92. nejomibot-0.99.37/nejomibot/locale/fi.mo +0 -0
  93. nejomibot-0.99.37/nejomibot/locale/fi.po +3816 -0
  94. nejomibot-0.99.37/nejomibot/locale/fi.toml +85 -0
  95. nejomibot-0.99.37/nejomibot/locale/fr.mo +0 -0
  96. nejomibot-0.99.37/nejomibot/locale/fr.po +3879 -0
  97. nejomibot-0.99.37/nejomibot/locale/fr.toml +83 -0
  98. nejomibot-0.99.37/nejomibot/locale/messages.pot +3462 -0
  99. nejomibot-0.99.37/nejomibot/locale/nb.mo +0 -0
  100. nejomibot-0.99.37/nejomibot/locale/nb.po +3790 -0
  101. nejomibot-0.99.37/nejomibot/locale/nb.toml +85 -0
  102. nejomibot-0.99.37/nejomibot/locale/sv.mo +0 -0
  103. nejomibot-0.99.37/nejomibot/locale/sv.po +3778 -0
  104. nejomibot-0.99.37/nejomibot/locale/sv.toml +85 -0
  105. nejomibot-0.99.37/nejomibot/locale/template.toml +104 -0
  106. nejomibot-0.99.37/nejomibot/logo.png +0 -0
  107. nejomibot-0.99.37/nejomibot/mentions.py +71 -0
  108. nejomibot-0.99.37/nejomibot/messages.py +799 -0
  109. nejomibot-0.99.37/nejomibot/notices.py +447 -0
  110. nejomibot-0.99.37/nejomibot/onthisday.py +124 -0
  111. nejomibot-0.99.37/nejomibot/photos.py +1064 -0
  112. nejomibot-0.99.37/nejomibot/ping.py +22 -0
  113. nejomibot-0.99.37/nejomibot/profile.py +641 -0
  114. nejomibot-0.99.37/nejomibot/publicip.py +143 -0
  115. nejomibot-0.99.37/nejomibot/receipts.py +107 -0
  116. nejomibot-0.99.37/nejomibot/relay.py +182 -0
  117. nejomibot-0.99.37/nejomibot/reminders.py +341 -0
  118. nejomibot-0.99.37/nejomibot/rest.py +227 -0
  119. nejomibot-0.99.37/nejomibot/selfpromote.py +49 -0
  120. nejomibot-0.99.37/nejomibot/specials.py +751 -0
  121. nejomibot-0.99.37/nejomibot/status.py +414 -0
  122. nejomibot-0.99.37/nejomibot/styles.py +285 -0
  123. nejomibot-0.99.37/nejomibot/system.py +237 -0
  124. nejomibot-0.99.37/nejomibot/terminal.py +470 -0
  125. nejomibot-0.99.37/nejomibot/timers.py +955 -0
  126. nejomibot-0.99.37/nejomibot/timers_cli.py +222 -0
  127. nejomibot-0.99.37/nejomibot/today.py +126 -0
  128. nejomibot-0.99.37/nejomibot/weather.py +558 -0
  129. nejomibot-0.99.37/pyproject.toml +116 -0
  130. nejomibot-0.99.37/pyproject.toml.orig +105 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jodab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ The fortune libraries in nejomibot/fortunes, all but fortunes_svenska, come
2
+ unchanged from fortune-mod (https://github.com/shlomif/fortune-mod), under
3
+ its license, as its COPYING.txt gives it:
4
+
5
+ Copyright (c) 1986, 1993
6
+ The Regents of the University of California. All rights reserved.
7
+
8
+ This code is derived from software contributed to Berkeley by
9
+ Ken Arnold.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions
13
+ are met:
14
+ 1. Redistributions of source code must retain the above copyright
15
+ notice, this list of conditions and the following disclaimer.
16
+ 2. Redistributions in binary form must reproduce the above copyright
17
+ notice, this list of conditions and the following disclaimer in the
18
+ documentation and/or other materials provided with the distribution.
19
+ 3. All advertising materials mentioning features or use of this software
20
+ must display the following acknowledgement:
21
+ This product includes software developed by the University of
22
+ California, Berkeley and its contributors.
23
+ 4. Neither the name of the University nor the names of its contributors
24
+ may be used to endorse or promote products derived from this software
25
+ without specific prior written permission.
26
+
27
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37
+ SUCH DAMAGE.
@@ -0,0 +1,504 @@
1
+ Metadata-Version: 2.4
2
+ Name: nejomibot
3
+ Version: 0.99.37
4
+ Summary: A Signal bot for a family or a group of friends
5
+ Keywords: signal,bot,signal-cli,self-hosted
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ License-File: LICENSES/fortune-mod.txt
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: No Input/Output (Daemon)
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.15
15
+ Classifier: Topic :: Communications :: Chat
16
+ Requires-Dist: signalbot==2.2.0
17
+ Requires-Dist: aiohttp>=3.11
18
+ Requires-Dist: pillow>=13.0
19
+ Requires-Dist: pillow-heif>=1.8
20
+ Requires-Dist: pydantic-settings>=2.11
21
+ Requires-Dist: python-dateutil>=2.9
22
+ Requires-Dist: tzdata
23
+ Requires-Dist: segno>=1.6.6
24
+ Requires-Python: >=3.15
25
+ Project-URL: Source, https://github.com/jodab/nejomibot
26
+ Project-URL: Documentation, https://github.com/jodab/nejomibot#readme
27
+ Project-URL: Changelog, https://github.com/jodab/nejomibot/blob/main/CHANGELOG.md
28
+ Project-URL: Issues, https://github.com/jodab/nejomibot/issues
29
+ Project-URL: Container image, https://hub.docker.com/r/jodab/nejomibot
30
+ Description-Content-Type: text/markdown
31
+
32
+ ![Project banner](https://raw.githubusercontent.com/jodab/nejomibot/main/nejomibot/logo.png)
33
+
34
+ # nejomibot
35
+
36
+ [![CI](https://github.com/jodab/nejomibot/actions/workflows/ci.yml/badge.svg)](https://github.com/jodab/nejomibot/actions/workflows/ci.yml)
37
+ [![Release](https://img.shields.io/github/v/release/jodab/nejomibot)](https://github.com/jodab/nejomibot/releases)
38
+
39
+ > **Disclaimer:** This project began as a playground for teaching myself to
40
+ > make better use of Claude Code. Everything here was written by AI, under my
41
+ > guidance.
42
+
43
+ A self-hosted Signal bot for a family or a group of friends: a daily post
44
+ with the day's holidays, birthdays and name days, the weather and a photo
45
+ from your library; commands for photos, the weather, reminders and shared
46
+ lists; and timers that run any command on a schedule. The admins manage it
47
+ from Signal.
48
+
49
+ - **Photos** at random, by album, date or moment, or from this day in an
50
+ earlier year, with when and where they were taken, and optionally an age.
51
+ - **The day:** holidays in 35 countries, each group's birthdays and name
52
+ days, and the weather and forecast for any place.
53
+ - **Your own commands:** timers, and specials that combine texts and
54
+ commands.
55
+ - **Nine languages**, chosen per chat.
56
+ - **Private:** it answers only those you allow, takes admin commands only
57
+ in a direct chat, and by default logs no phone numbers or message text.
58
+ - **Reliable and light:** health checks with external pings, an outbox that
59
+ retries and outlasts restarts, and about 80 MB of memory.
60
+
61
+ It talks to Signal through [signalbot](https://pypi.org/project/signalbot/)
62
+ and [signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api)
63
+ in json-rpc mode. **Image:** `docker.io/jodab/nejomibot` or
64
+ `ghcr.io/jodab/nejomibot`, tagged `latest` and with each version.
65
+ **Package:** `uv tool install nejomibot`, from
66
+ [PyPI](https://pypi.org/project/nejomibot/), for the bot and its tools
67
+ without a container. **Source:**
68
+ [github.com/jodab/nejomibot](https://github.com/jodab/nejomibot).
69
+
70
+ ## Getting started
71
+
72
+ You need a Linux host with Docker Compose or Podman (both containers take
73
+ about 450 MB of memory), an
74
+ [OpenWeatherMap API key](https://home.openweathermap.org/api_keys) (a new key
75
+ works within two hours), your photos (JPEG, PNG, GIF, WebP, AVIF or HEIC),
76
+ and a number for the bot that can receive a text message or a call and
77
+ isn't in use with Signal, as registering it signs it out everywhere else.
78
+ The bot can instead be
79
+ [linked to a phone](https://github.com/jodab/nejomibot#linking-to-a-phone).
80
+
81
+ ```sh
82
+ mkdir -p ~/nejomibot/{data,photos,signal} && cd ~/nejomibot
83
+ curl -fsSLO https://raw.githubusercontent.com/jodab/nejomibot/main/deploy/compose.yaml
84
+ docker run --rm docker.io/jodab/nejomibot cat /app/config.example.toml > data/config.toml
85
+ ```
86
+
87
+ In `data/config.toml`, set `account` (the bot's number, as `+46700000000`),
88
+ `admins` (yours), `openweather_api_key`, `weather_city`, `timezone` and
89
+ `language` (`en`, `da`, `de`, `el`, `es`, `fi`, `fr`, `nb` or `sv`); in
90
+ `compose.yaml`, the photos volume and `TZ`. Then:
91
+
92
+ ```sh
93
+ sudo chown -R 1000:1000 data signal && sudo chmod 700 data
94
+ docker compose up -d
95
+ docker compose exec nejomibot nejomibot-account setup
96
+ ```
97
+
98
+ Both containers run as UID 1000 (rootless Podman: `podman unshare chown`;
99
+ another user: `user:`, owning `data`), and the photos must be readable by
100
+ everyone. With SELinux, add `:Z` to the `data` and `signal` volumes and
101
+ `,z` to the photos volume.
102
+
103
+ The bot waits for its account. `nejomibot-account setup` registers the
104
+ number, with a code by text message or call, or links the bot to a phone. If
105
+ Signal asks for a captcha, solve one at
106
+ [signalcaptchas.org](https://signalcaptchas.org/registration/generate.html)
107
+ and paste the `signalcaptcha://` link behind **Open Signal** within a
108
+ minute. Give the bot a name, and a PIN, which keeps others from registering
109
+ its number. Then, from an admin's phone:
110
+
111
+ 1. Send the bot `/help`. Commands may start with `.`, `!` or `/`.
112
+ 2. Once the bot has replied, add it to a group (before that, Signal may only
113
+ invite it, which it doesn't accept), and allow the group from your direct
114
+ chat: `/allow Family`. People: `/allow +46700000002`.
115
+ 3. `/chats` numbers the chats; `/timer add 08:00 1 /today; /weather;
116
+ /uniquephoto` posts to chat 1 daily, and `/timer run 1` tries it.
117
+
118
+ With Podman and systemd, use the
119
+ [Quadlets](https://github.com/jodab/nejomibot#podman-quadlets): they also
120
+ restart a container whose health check fails.
121
+
122
+ **No reply?** `admins` must hold your number, or if your Signal hides it
123
+ from everyone, your Signal ID, which `nejomibot-account identities` lists.
124
+ **A group is missing from `/chats`?** Signal only invited the bot: revoke
125
+ the invitation, and add it again.
126
+
127
+ ## Commands
128
+
129
+ This page writes commands with `/`; the bot shows them with
130
+ `command_prefix`, `.` by default. `/help <command>` explains one, with
131
+ examples. Every language's names work in every chat (`/väder` is
132
+ `/weather`), and so do `/test` (`/status`), `/time` (`/info`), `/safety`
133
+ (`/contacts`) and `/day` (`/today`).
134
+
135
+ For everyone allowed:
136
+
137
+ | Command | Does |
138
+ |---|---|
139
+ | `/photo [<album>] [<date>]` | A random photo, taken in a period (`2017-05`, `2017-05-14 15`) or the nearest, or nearest a moment (`2024-06-01 15:00`) |
140
+ | `/sphoto [<album>] [<date>]` | The same, without a caption |
141
+ | `/uniquephoto [<album>]` | A photo not yet sent in the current round |
142
+ | `/onthisday [<album>]` | A photo from this day, or within three days of it, in an earlier year |
143
+ | `/albums` | The albums, with their photos, years, birth dates and shortcuts |
144
+ | `/cache [<album>]` | The number and size of the photos, by type |
145
+ | `/today` | The date and time, the holidays, and in a group, its birthdays and name days |
146
+ | `/weather [<city>]` | The weather in `weather_city`, or a place such as `Springfield,MO,US` |
147
+ | `/forecast [<city>]` | Today's and tomorrow's weather, temperatures and chance of rain or snow |
148
+ | `/info` | The time in the `clocks`, and the `exchange_rate` |
149
+ | `/ping` | `pong!`, and how long the message took |
150
+ | `/fortune [<library>]` | A fortune; `all`, or `list` for the libraries |
151
+ | `/remind <when> <text>` | A reminder in the chat (`18:00`, `30m`, `fri 17:00`, `2026-12-24 15:00`); alone, the list; `remove <n>` |
152
+ | `/list [add <items>]` | The chat's list: `add milk, eggs`, `remove 2,5`, `clear` |
153
+ | `/birthdays` | The coming birthdays: the group's, or all, for an admin in a direct chat |
154
+ | `/namedays` | The coming name days, likewise |
155
+ | `/language [<language>]` | The chat's language, set by an admin in a group; `default` resets it |
156
+ | `/help [<command>]` | The commands you may use, or one command's page |
157
+
158
+ For admins, in a direct chat:
159
+
160
+ | Command | Does |
161
+ |---|---|
162
+ | `/status` | The health checks, and the bot, account, network, container and host |
163
+ | `/album <album> [<birth date>]` | An album; sets its birth date (`-` removes it), or `shortcuts <names>` |
164
+ | `/today preview [<date>]` | What `/today` will say, in each group and a direct chat |
165
+ | `/contacts [<who>]` | Everyone the account knows, numbered; one person, with their safety number |
166
+ | `/contacts add <who> <name>` | Saves or renames a contact, also by number or `u:username` |
167
+ | `/trust [<who>] [<safety number>]` | The safety numbers; given one, marks it verified |
168
+ | `/allowed` | The allowed list |
169
+ | `/allow <who>` | Allows a person, or a group by name or ID |
170
+ | `/disallow <n>` | Takes someone off the allowed list |
171
+ | `/admins` | The admins |
172
+ | `/admin add <who> [<language>]` | Makes someone an admin; `/admin remove <n>` |
173
+ | `/profile` | The bot's name, About text, username and avatar |
174
+ | `/name <name>` | Sets the bot's name |
175
+ | `/about <text>` | Sets the About text, or `remove` |
176
+ | `/avatar` | Makes the picture sent with it the avatar, or `remove` |
177
+ | `/username <name>` | Sets the username, with a link to a chat with the bot, or `remove` |
178
+ | `/chats` | The chats that timers, `/msg` and `/run` post to, numbered |
179
+ | `/timers` | The timers |
180
+ | `/timer add <when> [<chats>] <commands>` | Adds a timer; `remove <n>`, `run <n>` |
181
+ | `/specials` | The specials |
182
+ | `/special add <name> <parts>` | Adds or changes a special; `shortcuts`, `everyone`, `admins`, `remove` |
183
+ | `/language <chats> <language>` | Sets the chats' language |
184
+ | `/msg <chats> <text>` | Posts a text to chats |
185
+ | `/run <chats> <commands>` | Posts commands' answers to chats |
186
+ | `/incoming [<n>,<n>]` | The latest direct messages from non-admins; forwards those given |
187
+ | `/birthday add <date> [<chats>] <who>` | Adds a birthday; `remove <n>` |
188
+ | `/nameday add <date> [<chats>] <who>` | Adds a name day; `remove <n>` |
189
+ | `/delete list` | The bot's latest messages, anywhere; `/delete <n>,<n>` deletes those |
190
+ | `/delivery` | How many received and read each of the bot's latest messages |
191
+ | `/selfpromote` | The bot's name and links, under its picture, to forward |
192
+
193
+ For admins, in any chat:
194
+
195
+ | Command | Does |
196
+ |---|---|
197
+ | `/delete` | Deletes the bot's latest message in the chat, then the one before |
198
+ | `/mention all [<text>]` | Mentions everyone in the group |
199
+
200
+ `/avatar` needs attachments, which the examples turn off
201
+ (`JSON_RPC_IGNORE_ATTACHMENTS=true`); `podman exec -i nejomibot
202
+ nejomibot-chat -a - /avatar < picture.png` sets one from the host. The
203
+ `bot_*` settings fill in what `data/bot.txt`, the profile the bot keeps,
204
+ lacks.
205
+
206
+ ## Who may use what
207
+
208
+ - **Admins:** those in `admins`, and those they add with `/admin add` (in
209
+ `data/admins.txt`). Admin commands work only in a direct chat, except
210
+ `/delete` and `/mention all`. Only the configuration's admins add and
211
+ remove admins and get the notices, and timers run as the first of them.
212
+ - **Everyone else** gets answers only if allowed: a person in a direct chat,
213
+ or everyone in an allowed group, in that group. Allowed people who send
214
+ something other than a command in a direct chat get a greeting, at most
215
+ once a day.
216
+ - **A limit:** the bot answers each person at most `command_limit` commands
217
+ (20) in `command_limit_minutes` (10); the admins, timers, `/run` and
218
+ specials aren't limited.
219
+ - **Edits:** an edited command runs in place of the original, within a day,
220
+ undoing what the original added or removed; posts and deletions aren't
221
+ repeated.
222
+
223
+ ## Timers
224
+
225
+ ```
226
+ /timer add 08:00 1,2 /today; /weather; /uniquephoto daily, to chats 1 and 2 of /chats
227
+ /timer add mon-fri 07:30 1 /weather
228
+ /timer add tomorrow 18:00 1 /photo once
229
+ /timer add every 2h /status without chats: to you
230
+ ```
231
+
232
+ `<when>` is times (`08:00,18:30`), days and a time (`mon-fri`, `sat,sun`,
233
+ `weekdays`, `weekends`), a date and a time (`2026-12-24 15:00`, `tomorrow
234
+ 08:00`), or an interval of 10 minutes or more (`every 2h`), in `timezone`.
235
+ The commands, separated by `;`, run as the first admin's in a direct chat,
236
+ and every chat gets the same photo. Timers can't run what changes timers,
237
+ specials, birthdays, albums or access, `/msg`, `/run`, a chat's own commands
238
+ (`/remind`, `/list`, `/birthdays`, `/namedays`), `/delete` or `/contacts
239
+ add`. A run missed while the bot was down is made at its start, up to an
240
+ hour late. `nejomibot-timers` edits `data/timers.txt` from a shell.
241
+
242
+ ## Specials
243
+
244
+ ```
245
+ /special add /morning Good morning! /today + /weather; /uniquephoto
246
+ /special shortcuts /morning /gm
247
+ /special everyone /morning
248
+ ```
249
+
250
+ `;` or a line break starts a new message, a `+` on its own joins the parts
251
+ around it into one, and text before a command heads its answer, formatted as
252
+ given in Signal. A special has up to five parts and five shortcuts, can hold
253
+ any command a timer can run, but no special, and runs as whoever sends it.
254
+ `/special everyone` lets everyone allowed use it; the bot names any admin
255
+ commands in it, whose answers they would see.
256
+
257
+ ## Photos
258
+
259
+ - Each folder directly in `photos_dir` is an album. `/album Alice shortcuts
260
+ /alice` makes `/alice` mean `/photo Alice`.
261
+ - `/uniquephoto` sends every photo once before it starts a new round, and
262
+ each album has its own round. The rounds are in `data/sent-photos.txt` and
263
+ `data/album-rounds.txt`: delete the file, or the album's lines, to start
264
+ over.
265
+ - When each photo was taken is read in the background at start, from new
266
+ and changed files, into `data/photo-index.txt` and memory (about 400 bytes
267
+ a photo; 47 s for 7,671 photos on a Raspberry Pi 4).
268
+ - Captions show the time in `timezone`. DateTimeOriginal counts as UTC when
269
+ DateTimeDigitized is a whole time zone away from it; otherwise its offset
270
+ tag applies, or else `photos_timezone`.
271
+ - With a birth date (`/album Alice 2019-06-15`, or `photos_birth_date` for
272
+ the photos in no album), captions show the age.
273
+ - HEIC/HEIF is sent as JPEG, unless `photos_heif = false` leaves it out;
274
+ [tools/tidy-photos.sh](https://github.com/jodab/nejomibot/blob/main/tools/tidy-photos.sh)
275
+ converts, renames and deduplicates a library.
276
+ - A GPS position adds a map link (`photos_map`).
277
+
278
+ ## Birthdays, name days and holidays
279
+
280
+ `holidays = ["SE", "GR"]` adds those countries' public holidays and common
281
+ observances to `/today`, from AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, EE, ES,
282
+ FI, FR, GB, GR, HR, HU, IE, IS, IT, LT, LU, LV, MT, NL, NO, NZ, PL, PT, RO,
283
+ SE, SI, SK and US; only Sweden's and Greece's, and the days others share
284
+ with them, are translated. Birthdays and name days show in groups only: a
285
+ contact's (`/birthday add 1990-03-14 Alice`) in the groups they're in, and
286
+ others' (`/birthday add 1940-05-01 1 Grandma`) in the chats given, or in
287
+ every group.
288
+
289
+ ## Languages
290
+
291
+ `language` sets the bot's language, and `/language sv` a chat's own. Each is
292
+ two files in
293
+ [nejomibot/locale](https://github.com/jodab/nejomibot/tree/main/nejomibot/locale):
294
+ `<code>.po`, the messages, and `<code>.toml`, its words for commands, times
295
+ and subcommands. `tools/translations.sh <code>` starts one, and after the
296
+ messages change, updates every `.po` (it needs GNU gettext).
297
+
298
+ ## The bot from a terminal
299
+
300
+ The image's tools run with `podman exec` or `docker compose exec`; each has
301
+ `--help`, and a shell in the container starts with a guide to them (`help`):
302
+
303
+ ```sh
304
+ podman exec -it nejomibot nejomibot-chat # as the first admin's direct chat
305
+ podman exec nejomibot nejomibot-chat /weather Helsinki # one message and its answers
306
+ podman exec -it nejomibot nejomibot-chat --to 1 # chat 1 of /chats, as the bot
307
+ podman exec -i nejomibot nejomibot-chat -a - "/msg 1 Look!" < photo.jpg
308
+ podman exec nejomibot nejomibot-chat --recent 20 --follow # messages received, in every chat
309
+ podman exec nejomibot nejomibot-timers # the timers
310
+ ```
311
+
312
+ `--json` prints a JSON object a line, for scripts. `nejomibot-chat` reaches
313
+ the bot over an abstract Unix socket, which only its user and root may use.
314
+
315
+ ## The Signal account
316
+
317
+ `nejomibot-account` registers or links the account, and manages its
318
+ profile, username, PIN, privacy, linked devices, safety numbers and rate
319
+ limits; `--dry-run` sends nothing.
320
+
321
+ ```sh
322
+ podman exec -it nejomibot nejomibot-account # the status
323
+ podman exec -i nejomibot nejomibot-account profile --avatar - < logo.png
324
+ podman run --rm -it --no-healthcheck --network container:signal \
325
+ docker.io/jodab/nejomibot nejomibot-account setup # without the bot
326
+ ```
327
+
328
+ When Signal rate-limits the bot, `nejomibot-account rate-limit --to <your
329
+ number>` leads you through the captcha that lifts the limit. Without the
330
+ image, `uvx --from nejomibot nejomibot-account --url HOST:PORT` runs it on
331
+ any machine that reaches signal-cli-rest-api.
332
+
333
+ ### Linking to a phone
334
+
335
+ `nejomibot-account link` shows a QR code to scan in Signal on the phone
336
+ (**Settings > Linked devices**). The bot then needs no number, but what is
337
+ typed on the phone in a direct chat reaches it as if the other person had
338
+ sent it, as signalbot can't tell them apart, so it runs their commands, with
339
+ an admin's rights in a chat with an admin. The phone must come online every
340
+ 30 days, or Signal unlinks the bot, and only the phone can set the PIN.
341
+
342
+ ### An account without a phone number
343
+
344
+ Signal Android's beta registers accounts without a number, with a Signal
345
+ Login: an Account ID, which the bot calls the Signal ID, and a Recovery key.
346
+ Set `account` to the Signal ID, and give the bot a username, by which alone
347
+ people can find it. signal-cli-rest-api can link to such an account but not
348
+ restore it; signal-cli's `register --recovery-key` can, on
349
+ signal-cli-rest-api's data while it's stopped. People without a number go
350
+ by their Signal ID in `admins`, `/allow` and `/admin add`.
351
+
352
+ ## Health checks
353
+
354
+ Every `healthcheck_minutes` (5), the bot checks signal-cli-rest-api (it
355
+ answers, in json-rpc mode, newer than 0.101), itself (no message has waited
356
+ 15 minutes, and since the last round none was given up or failed with an
357
+ unknown outcome), Signal's servers (they list the linked devices), and
358
+ delivery (it hears receipts, and no three messages in a row have gone
359
+ unconfirmed for `unconfirmed_hours`, 12, with none confirmed since).
360
+ Failures are logged and lead `/status`; each round pings `healthcheck_url`,
361
+ such as a [healthchecks.io](https://healthchecks.io) URL, or its `/fail`,
362
+ with the results. The configuration's admins hear from the bot at each start
363
+ (`startup_notice`), at a rate limit, and when a safety number changes.
364
+
365
+ The image's health check is for liveness only: it never fails for Signal
366
+ or signal-cli-rest-api, which a restart wouldn't mend, nor while the bot
367
+ waits for its account. The Quadlets restart a container that fails it three
368
+ times running; Docker doesn't.
369
+
370
+ ## Sending and Signal's rate limits
371
+
372
+ Each chat gets its messages in order. A send that surely failed is tried
373
+ again for `send_retry_hours` (3), marked late; a timeout isn't, as the
374
+ message may have arrived. Messages that have waited 30 seconds, and all
375
+ waiting at shutdown, are kept in `data/outbox` until sent. When Signal
376
+ rate-limits the bot (429), solving a captcha lifts the limit; meanwhile the
377
+ bot sends no receipts or typing, and tries only the oldest message, after 2
378
+ minutes, then twice as long each time, up to `rate_limit_retry_minutes`
379
+ (30), or soon after an admin's command.
380
+
381
+ ## Which signal-cli-rest-api
382
+
383
+ The bot needs one newer than 0.101, the latest release, which crashes on
384
+ overlapping requests
385
+ ([#885](https://github.com/bbernhard/signal-cli-rest-api/issues/885)) and
386
+ logs every message it receives
387
+ ([#892](https://github.com/bbernhard/signal-cli-rest-api/issues/892)). Until
388
+ the next release, the examples in
389
+ [deploy/](https://github.com/jodab/nejomibot/tree/main/deploy) pin the CI
390
+ build `1792710189-ci` by digest; it reports its version as `unset`. Run it
391
+ in `json-rpc` mode: `json-rpc-native` has had bugs `json-rpc` hasn't, and
392
+ has no 32-bit ARM build. It has no authentication, so whoever reaches it
393
+ controls the account: the examples publish no port.
394
+
395
+ ## Configuration
396
+
397
+ [config.example.toml](https://github.com/jodab/nejomibot/blob/main/config.example.toml)
398
+ (`/app/config.example.toml` in the image) describes every setting, aliases
399
+ included. The bot reads `NEJOMIBOT_CONFIG` (`/data/config.toml` in the
400
+ image), and `NEJOMIBOT_<SETTING>` variables override it (lists and tables as JSON).
401
+ An unknown or invalid setting stops the bot with its name.
402
+
403
+ `data_dir` (`/data`) must exist and belong to the bot's user. The bot
404
+ writes its files there atomically, readable by that user alone:
405
+
406
+ | File | Holds |
407
+ |---|---|
408
+ | `allowed.txt`, `admins.txt` | The allowed list, and the admins added |
409
+ | `timers.txt`, `specials.txt` | The timers and specials |
410
+ | `reminders.txt`, `lists.txt`, `languages.txt` | Each chat's reminders, list and language |
411
+ | `birthdays.txt`, `albums.txt` | Birthdays and name days; albums' birth dates and shortcuts |
412
+ | `sent-photos.txt`, `album-rounds.txt` | `/uniquephoto`'s rounds |
413
+ | `photo-index.txt` | When each photo was taken |
414
+ | `bot.txt` | The profile, with the avatar beside it |
415
+ | `outbox/`, `run.json` | Messages waiting; the last run's version and stop |
416
+
417
+ You may edit the allowed list, admins, timers, specials, reminders, lists,
418
+ birthdays, albums and languages by hand while the bot runs: the bot reads
419
+ them again when they change, and leaves comments and lines it can't read as
420
+ they are. `bot.txt` takes effect at the next start.
421
+
422
+ ## Running it
423
+
424
+ The image runs as UID and GID 1000, or the user it's given, needs no
425
+ capabilities, and writes only to `/data`, which must belong to its user
426
+ (Podman's `:U`), and `/tmp`: the examples run it read-only, without new
427
+ privileges, with an init process. The photos go in `/photos`, read-only.
428
+
429
+ ### Podman Quadlets
430
+
431
+ Copy [signal.container](https://github.com/jodab/nejomibot/blob/main/deploy/signal.container)
432
+ and [nejomibot.container](https://github.com/jodab/nejomibot/blob/main/deploy/nejomibot.container)
433
+ to `/etc/containers/systemd/` (rootless: `~/.config/containers/systemd/`,
434
+ `UserNS=keep-id:uid=1000,gid=1000` in place of `:U`, and `loginctl
435
+ enable-linger`), adjust the paths, and run `systemctl daemon-reload &&
436
+ systemctl start signal`, which starts the bot too. The bot shares
437
+ signal-cli-rest-api's network namespace, reaching it at `127.0.0.1:8080`
438
+ where nothing else can, and stops and starts with it. `signal.container`
439
+ checks signal-cli itself, where the image's own check only reaches the REST
440
+ layer.
441
+
442
+ ### Updating
443
+
444
+ `docker compose pull && docker compose up -d`, or `podman pull
445
+ docker.io/jodab/nejomibot && systemctl restart nejomibot`. The
446
+ [changelog](https://github.com/jodab/nejomibot/blob/main/CHANGELOG.md) says
447
+ what to do, if anything, and a setting that must change stops the bot with
448
+ its name. A version tag in place of `latest` lets you choose when.
449
+
450
+ ### Logs and verification
451
+
452
+ At `INFO`, the bot logs commands by name and the kind of chat, never phone
453
+ numbers, IDs or message text, which `log_level = "DEBUG"` adds; libraries
454
+ log at `WARNING`, where signalbot names whom a group message didn't reach.
455
+ Each release's images have signed build provenance;
456
+ [SECURITY.md](https://github.com/jodab/nejomibot/blob/main/SECURITY.md) has
457
+ more, and how to report a vulnerability:
458
+
459
+ ```sh
460
+ gh attestation verify oci://docker.io/jodab/nejomibot:latest --repo jodab/nejomibot
461
+ ```
462
+
463
+ ## Development
464
+
465
+ ```sh
466
+ uv sync # .venv: Python 3.15 and the locked dependencies
467
+ uv run pytest # the tests, against a stand-in signal-cli-rest-api
468
+ uv run ruff check && uv run ruff format --check && uv run pyright
469
+ tools/build-image.sh # the checks, then the amd64, arm64 and arm/v7 images
470
+ ```
471
+
472
+ CI runs these, ShellCheck and an amd64 build on pushes to main and pull
473
+ requests; `tools/release.sh` releases what it passed.
474
+ [AGENTS.md](https://github.com/jodab/nejomibot/blob/main/AGENTS.md)
475
+ describes the design and the project's rules.
476
+
477
+ ### Running from a checkout
478
+
479
+ The bot and its tools run on Python alone, without a container, and the
480
+ tests need nothing more. The bot needs a signal-cli-rest-api at
481
+ `signal_service`, such as the server's, published on its loopback
482
+ (`PublishPort=127.0.0.1:8080:8080` in `signal.container`) and reached with
483
+ `ssh -L 8080:127.0.0.1:8080 server`. Stop the server's bot first: two bots
484
+ on one account compete for its messages.
485
+
486
+ ```sh
487
+ cp config.example.toml config.toml # account, admins, debug_group, ...
488
+ NEJOMIBOT_DEBUG=1 NEJOMIBOT_PHOTOS_DIR=~/Pictures uv run nejomibot
489
+ uv run nejomibot-chat /help # in another terminal, as the first admin
490
+ ```
491
+
492
+ Debug mode talks only to `debug_group` and the admins, and keeps its files
493
+ in a new temporary folder; for lasting ones, `mkdir -m 700 data` and set
494
+ `NEJOMIBOT_DATA_DIR=data`. Without uv, a Python 3.15 virtual environment
495
+ with `pip install -e . --group dev` (pip 25.1 or later) works too.
496
+
497
+ ## License
498
+
499
+ MIT ([LICENSE](https://github.com/jodab/nejomibot/blob/main/LICENSE));
500
+ releases up to 0.99.14 were GPL-3.0-or-later, and remain so. The fortune
501
+ libraries but `fortunes_svenska`, the bot's own, come from
502
+ [fortune-mod](https://github.com/shlomif/fortune-mod), as Debian ships them,
503
+ under its BSD license
504
+ ([LICENSES/fortune-mod.txt](https://github.com/jodab/nejomibot/blob/main/LICENSES/fortune-mod.txt)).