Amnotbot is a Java chat bot with IRCv3, classic IRC, and XMPP connections. It responds to commands, previews shared links and Spotify URIs, saves bookmarks, and polls GitHub repositories and a Twitter home timeline.
This reference describes the implementation and bundled configuration in this repository. External integrations require working credentials and compatible services; their live availability has not been verified as part of this review.
Use JDK 21 and Maven 3.9 or newer. Set JAVA_HOME to your JDK 21 installation
and verify that mvn -version reports Java 21. Maven uses --release 21 to
compile against Java 21 APIs and produce Java 21 bytecode. Running the bot
requires Java 21 or newer.
mvn clean verifyThe executable JAR includes dependencies:
java -jar target/amnotbot-core-0.0.1-SNAPSHOT.jarBefore starting, create ~/.amnotbot and copy any missing configuration files
from src/main/resources: amnotbot.config, commands.config, and
tasks.config. Edit those copies for your installation. The bot also copies
missing files on startup, but its first-run welcome message does not stop
execution, so configure it before connecting.
Existing configuration files are preserved when upgrading. Changes to bundled
resources do not update existing installations, and configuration changes require
a restart. Environment variables are resolved through ${env:VARIABLE} entries;
the bot does not load a .env file automatically.
For the bundled IRCv3 connection, set these variables in the environment of the process or service that launches Java:
| Variable | Purpose |
|---|---|
AMNOTBOT_SERVER |
IRC server hostname. |
AMNOTBOT_SERVER_PORT |
Server port, matching the selected TLS mode. |
AMNOTBOT_SERVER_SSL |
true for TLS, false for plaintext. |
AMNOTBOT_CHANNELS |
Channel list; see the multiple-channel note below. |
AMNOTBOT_NICK |
Bot nickname. |
AMNOTBOT_ACCOUNT_NAME |
SASL account name. |
AMNOTBOT_ACCOUNT_PASSWORD |
SASL account password. |
IRCv3 always registers SASL PLAIN authentication with the configured account. For a classic IRC configuration, NickServ settings are described below. Disable unused commands and scheduled tasks before starting: missing credentials do not automatically disable most integrations.
The repository also contains a Dockerfile that builds and runs the JAR as the
amnotbot user, and a Procfile defining the same command as a worker. The
Dockerfile uses Maven 3.9 with Eclipse Temurin 21. CI and the Heroku runtime
also use Java 21. Container configuration lives in
/home/amnotbot/.amnotbot.
Send commands in a channel the bot has joined. The default IRCv3 listener handles channel messages only, not direct messages. Classic IRC and XMPP also forward private messages to the interpreter.
Command patterns are case-insensitive regular expressions, evaluated with
substring matching (find()). Start explicit commands at the beginning of your
message: most handlers take parameters from everything after the first space, even when a
command matched later in the text. YouTube instead requires !yt at the start
and parses its own arguments. Multiple matching handlers run independently, and
reply ordering is not guaranteed.
| Command or message | Handler | Behavior |
|---|---|---|
!help |
Built-in interpreter | Lists loaded trigger patterns. |
!help weather |
Built-in interpreter | Searches trigger text using a case-insensitive regex and calls each matching handler's help method. |
!version |
VersionCommand |
Prints application name, version, and build revision. |
!ddg Java |
DuckDuckGoSearchCommand |
Queries DuckDuckGo's instant-answer endpoint and prints the heading and abstract URL, or Not found!. It does not return a general web-results list. |
!yt guitar heroe |
YouTubeCommand |
Posts the first relevance-ranked YouTube video with URL, title, duration, publication date (UTC), views, and likes. !yt alone shows usage. |
!g Java documentation |
GoogleWebSearchCommand |
Uses Google Custom Search and sends the first result's title, URL, and snippet as three messages. |
recieve (sp?) |
GoogleSpellingSearchCommand |
Looks up the word immediately before (sp?) using Google Custom Search and prints correctedQuery when returned. Use the space shown in this example. |
!weather Buenos Aires,AR |
WeatherCommand |
Looks up current weather by city and optional country code. !weather London also works. |
!price |
CryptoCoinPriceCommand |
Queries the Blockchain.com exchange ticker for BTC-USD. |
!price ETH |
CryptoCoinPriceCommand |
Uses the supplied base symbol with USD. |
!price ETH BTC |
CryptoCoinPriceCommand |
Uses the supplied base and quote symbols, uppercased. |
!quote |
QuoteCommand |
Retrieves a random stored quote, including its channel, submitting nickname, and date. Requires the quote database; see limitations below. |
!quote Something worth remembering |
QuoteCommand |
Stores the text, server, channel, submitting nickname, and timestamp. There is no success reply. |
!gpt Explain recursion briefly |
OpenAICommand |
Sends the prompt to the configured chat model and posts the first response, replacing line breaks with spaces. Each request contains one user message; there is no conversation history. |
Weather output includes available sky description, current and maximum/minimum
temperature, humidity, pressure, rain, cloud cover, sunrise/sunset, and wind speed
and direction. Set openweather_unit to metric for Celsius and meters/second;
other values select imperial units. The current formatter labels rain with %.
Price output includes the symbol pair, last trade price, and price 24 hours ago
(not a percentage change). Its formatter always prefixes prices with $, even
when the quote currency is not USD. Use one space between symbols.
!help is a case-sensitive prefix check, unlike ordinary command matching.
!help <pattern> accepts a regex, not a command name lookup, and invalid regexes
are not handled. Detailed help is incomplete: version and GPT throw an
unsupported-operation exception; Spotify and price return null; quote returns
an empty string. Some older help templates expose raw regexes or missing trigger
values. Use this README as the command reference.
!yt guitar heroe posts the first video ranked by relevance, with its URL,
title, duration, publication date (UTC), views, and likes. For example:
https://www.youtube.com/watch?v=abcdefghijk | Guitar Hero | Duration: 4:09 | Published: 2020-02-03 | Views: 12345 | Likes: 678
The example is illustrative. Results use the official YouTube Data API v3
and can differ from personalized youtube.com searches. Live and upcoming
videos are labeled; unavailable metadata is shown as unavailable, not zero.
Long titles are shortened to keep the reply suitable for IRC.
Enable YouTube Data API v3 in a Google Cloud project, create an API key,
and set AMNOTBOT_YOUTUBE_API_KEY in the bot's environment. See Google's
setup guide.
No user OAuth login or additional Java dependencies are required.
Existing installations must add these entries to their configuration files
under ~/.amnotbot/ (or their configured home directory), then restart:
In amnotbot.config:
youtube_api_key = ${env:AMNOTBOT_YOUTUBE_API_KEY}In commands.config (the doubled backslash is intentional):
YouTubeCommand = ^!yt(?:\\s+.*)?$Each successful search uses one search.list request with type=video,
order=relevance, and maxResults=1, followed by one videos.list request
for snippet,contentDetails,statistics. Search requests have daily limits;
check your project's quotas and Google's
quota documentation.
Missing keys, empty results, and API failures produce concise channel replies.
These handlers require no ! command. They run on matching messages, including
messages that also contain explicit commands, subject to the same rate limits.
| Feature | Handler | Behavior |
|---|---|---|
| Web page title | WebPageInfoCommand |
Fetches the first HTTP(S) URL and replies with [ page title ] when a title is available. Page metadata uses an in-memory LRU cache of 128 entries by default. |
| Long URL shortening | QurlRequestCommand |
Sends the first URL to the hard-coded Qurl HTTP API when its length exceeds qurl_length (83 characters by default). Replies with the sender's nickname, shortened URL, and domain when extracted. |
| Bookmark saving | BookmarkCommand |
Posts the first HTTP(S) URL to the configured bookmark backend and replies Bookmarked: <url> after a successful request. Disabled unless both backend URL and token are set. |
| Spotify artist | SpotifyCommand |
spotify:artist:<id> prints artist name and popularity/ranking. |
| Spotify album | SpotifyCommand |
spotify:album:<id> prints album name, artists, release date, and track count. |
| Spotify track | SpotifyCommand |
spotify:track:<id> prints track name, artists, duration, and preview URL. |
Use a lowercase Spotify URI with no trailing text. The parser expects an artist,
album, or track URI; ordinary Spotify HTTPS links go through the URL handlers.
Unsupported URI types receive Valid types: artist, album or track (second field).
The URL handlers process only the first URL in a message. The title and shortening handlers retain older, more restrictive trigger patterns than the bookmark handler, so their URL coverage can differ. The URL extractor stops at whitespace or angle brackets and does not strip trailing punctuation.
Set bookmark_backend_url to the backend's base URL without a trailing slash,
and bookmark_jwt_token to a valid login token. The request is:
POST <bookmark_backend_url>/api/bookmarks
Content-Type: application/json
Cookie: access_token=<bookmark_jwt_token>
{"url":"<first URL in the message>"}
Missing, blank, or unresolved ${...} settings silently disable this feature.
Request failures are logged without a channel error message. The bot does not
refresh tokens or deduplicate submissions locally. Replace an expired token in
the configuration or service environment and restart the bot.
tasks.config maps task classes to positive intervals in minutes. Each
connection creates its own task manager. The first execution is scheduled after
one minute, followed by the configured interval. Comment out a task's line with
# to disable it; restart after editing.
| Task | Bundled interval | Behavior and configuration |
|---|---|---|
GithubTask |
30 minutes | Polls public repository commits using the unauthenticated GitHub API. Set github_repos to comma-separated owner:repo entries and github_commits to the number inspected per repository (default 5). The first poll records existing entries without announcing them; later polls post unseen entries to all configured channels with repository, commit message, author email, and date. |
TwitterTask |
10 minutes | Polls the authenticated account's home timeline using Twitter4J. The first successful poll records existing statuses; subsequent polls announce unseen statuses as @screenName: text, flattening newlines. Requires all four Twitter OAuth settings. |
Task history is in memory and resets when the task is recreated. GitHub currently
uses tree SHAs for deduplication, so different commits with the same tree can be
collapsed; it also assumes at least github_commits results are returned. Twitter
stores up to 1,024 hashes of author/text. Its deduplication happens inside the
channel loop, so each new status is currently sent only to the first configured
channel. Both tasks are enabled in the bundled file even without credentials or
repository settings.
~/.amnotbot/amnotbot.config contains connection and integration settings.
Literal values can replace the environment placeholders in the bundled file.
| Config key | Bundled environment variable or default |
|---|---|
nick |
AMNOTBOT_NICK |
account_name, account_password |
AMNOTBOT_ACCOUNT_NAME, AMNOTBOT_ACCOUNT_PASSWORD |
nickserv_enabled |
AMNOTBOT_NICKSERV_ENABLED |
nickserv |
NICKSERV |
nickserv_password |
AMNOTBOT_NICKSERV_PASSWORD |
help_trigger |
!help |
qurl_length |
83 |
webpages_cache_size |
128 |
language, country |
en, US (bundled message/help resources) |
google_search_engine_id |
AMNOTBOT_GOOGLE_SEARCH_ENGINE_ID |
google_search_key |
AMNOTBOT_GOOGLE_SEARCH_KEY |
openweather_key |
AMNOTBOT_OPENWEATHER_KEY |
openweather_unit |
AMNOTBOT_OPENWEATHER_UNIT (metric or imperial) |
spotify_client_id |
AMNOTBOT_SPOTIFY_CLIENTID |
spotify_client_secret |
AMNOTBOT_SPOTIFY_CLIENTSECRET |
youtube_api_key |
AMNOTBOT_YOUTUBE_API_KEY |
openai_secret_key |
OPENAI_SECRET_KEY |
openai_model |
OPENAI_MODEL (no model default) |
bookmark_backend_url |
AMNOTBOT_BOOKMARK_BACKEND_URL |
bookmark_jwt_token |
AMNOTBOT_BOOKMARK_JWT_TOKEN |
github_repos |
AMNOTBOT_GITHUB_REPOS |
github_commits |
5 |
twitter_key, twitter_secret |
AMNOTBOT_TWITTER_KEY, AMNOTBOT_TWITTER_SECRET |
twitter_token, twitter_token_secret |
AMNOTBOT_TWITTER_TOKEN, AMNOTBOT_TWITTER_TOKEN_SECRET |
Connection keys follow <protocol>.<connection-name>.<setting>. Supported
protocol names are ircv3, irc, and xmpp. The bundled freenode connection
name is just a label; AMNOTBOT_SERVER determines its actual host. Multiple
connection groups can be configured simultaneously and share global command and
integration settings.
IRC/IRCv3 groups accept server, port (6667 if omitted), ssl, and channels.
Configure TLS explicitly with the appropriate server port. For multiple channels,
write a literal list in the config:
ircv3.freenode.channels = #amnotbot, #amnottestingCommons Configuration splits literal comma-separated values when loading the
file. Do not assume a comma-separated value inside one interpolated environment
variable will become multiple list entries; use literal lists for channels and
github_repos when configuring multiple values.
Classic IRC sends IDENTIFY <password> to nickserv when nickserv_enabled is
true. It also supports a global auto_rejoin = true setting after a kick; this
key is absent from the bundled config, so set it explicitly when using classic
IRC. These features are separate from IRCv3 SASL authentication.
XMPP groups accept server, port (5222 if omitted), channels (room JIDs),
user, password, and resource. The commented example in the bundled config
shows the shape. XMPP supports room and incoming private chat messages; room
joins request no history. This legacy adapter enables compression, disables
SASL, and allows self-signed certificates.
~/.amnotbot/commands.config maps the handler class names listed above to regex
triggers. Comment out a line with # to disable the handler. Editing a trigger
changes which messages reach the handler, but does not change its argument
parser. Escape backslashes for the properties format (for example \\S+) and
escape commas in regexes as the bundled file does. There is no global command
prefix setting; help_trigger only controls help.
Quote storage reads these environment variables directly:
JDBC_DATABASE_URL, for examplejdbc:postgresql://localhost:5432/amnotbot.JDBC_DATABASE_USERNAME.JDBC_DATABASE_PASSWORD.
The bundled Hibernate configuration selects PostgreSQL and maps Quote to
QUOTES with id, QUOTE_DATE, server, channel, nick, and text fields.
It does not configure automatic schema creation or provide migrations; provision
a schema compatible with src/main/java/com/github/amnotbot/hbm/Quote.hbm.xml.
Random retrieval selects from the entire table, with no server/channel filter.
The implementation uses order by rand() despite the PostgreSQL configuration,
and accesses the first result without checking for an empty table. Treat quote
retrieval as requiring database validation/fixes before depending on it.
- Rate limiting: matching messages are limited per connection and target, with a three-second minimum gap, a global threshold of ten requests per minute, and a per-nickname threshold of three per minute. Rejected messages are silently ignored. Automatic URL handlers count too; help bypasses this detector.
- Long replies: IRCv3 splits outgoing text into chunks of at most 392 Java characters, preferring spaces. Other adapters do not use this splitter.
- Connections: classic IRC and XMPP participate in the bot's five-second disconnected-connection check. IRCv3 reports itself connected unconditionally to that check and relies on its client library's connection lifecycle. Task managers are cancelled on IRC/IRCv3 disconnect and recreated on connection.
- Logging: Log4j defaults to debug-level console output. Classic IRC/XMPP
logging creates files under
~/.amnotbot/log/<server>/; IRCv3 prints timestamped raw incoming/outgoing lines to stdout. The Spotify implementation also prints its access token to stdout, so logs can contain credentials as well as chat. - No reply: check rate limits, whether the handler is enabled, credentials, and console logs. Many API errors are only logged. Google assumes a first result or spelling object exists, and weather replies only to valid results.
- Integration scope: GPT uses the Chat Completions endpoint with the configured model. DuckDuckGo and Qurl use hard-coded HTTP URLs. Provider/API changes may require code changes; configuration alone does not guarantee compatibility.
Run mvn test for the Java tests and mvn package to build the executable JAR.
Tests cover command interpretation, configuration helpers, URL extraction,
command-option parsing, bookmark HTTP behavior, and YouTube search/formatting; they do not establish that
all external integrations work live.
To add a command, implement BotCommand in com.github.amnotbot.cmd with a
public no-argument constructor and register it in commands.config. To add a
scheduled task, extend BotTask in com.github.amnotbot.task and register its
interval in tasks.config. Existing installations need the same registration in
their ~/.amnotbot copies. See HACKING.md for repository coding conventions.
Set these options in ~/.amnotbot/amnotbot.config.
IRCv3 also accepts untrusted-certificates (default false). To connect with
TLS to a server using a self-signed certificate, configure that connection:
ircv3.sekurnet.ssl = true
ircv3.sekurnet.port = 6697
ircv3.sekurnet.untrusted-certificates = trueThis keeps TLS encryption but disables certificate trust validation for that
connection, accepting any certificate, not only self-signed ones. It has no
effect when ssl = false. Classic IRC already accepts untrusted certificates
through its existing trust manager.
- Jimmy Mitchener jcm@packetpan.org
- Geronimo Poppino gpoppino@outlook.com