Vigila Your server's memory, enforced.
A Discord moderation bot built for communities that care about accountability. Log verbal warnings, host staff evaluation polls, and migrate from legacy tools.
Want to self-host or learn more? The documentation has everything you need.
What is Vigila?
Vigila (Latin: to watch, to keep guard) is a Discord moderation bot designed for server staff who need reliable, queryable verbal warning records. It was previously known as Verbal Warning Logger — renamed to reflect its broader scope with the introduction of the staff evaluation poll feature and to establish a more distinct identity.
Verbal Warning Logging
Record verbal warnings with reasons, timestamps, and moderator attribution.
Staff Evaluation Polls
Conduct and manage staff evaluation polls to gather feedback and improve moderation practices.
SQLite Database
All data is stored in your own SQLite database locally. You own your data, always.
Open Source & Self-Hostable
Deploy Vigila on your own infrastructure. Full documentation available.
Why Auttaja + Supabase?
Auttaja was a popular Discord moderation bot that shut down its public service,
leaving many communities with years of punishment history trapped in a
punishments.json
export file with no easy way to use it going forward.
Vigila was built with support for these communities. Rather than starting from scratch, you can import your entire Auttaja punishment history into your own Supabase database in seconds, preserving every warning, mute, kick, and ban record your staff ever issued.
The Supabase integration was chosen specifically because it gives you full control over
your data. Your punishments table is yours, readable by standard PostgreSQL tooling, exportable
at any time, and secured with Supabase's row-level security (RLS).
The auttaja.py cog bundled with Vigila is mostly read-only. It queries
your migrated Auttaja data without modifying it, so your historical records remain exactly as
Auttaja left them. You have the option to edit the reasons for past punishments through the
/auttaja edit command if you want to clean up any old records.
Migrating from Auttaja
Follow these steps to import your Auttaja punishments.json into
Supabase.
Create the Supabase Table
Open your Supabase project, go to the SQL Editor, and run the following:
create table punishments (
id text primary key,
guild_id text not null,
offender text not null,
punisher text,
reason text,
action text, -- normalised to lowercase
timestamp timestamptz,
duration integer, -- converted from string to int (seconds)
deleted boolean default false,
removed_by text,
removed_reason text,
resolve text -- references another punishment id
);
alter table punishments enable row level security;
Install the Supabase JS Client
In your terminal, navigate to the folder containing your punishments.json
and run:
npm install @supabase/supabase-js
Create import-punishments.mjs
Save the following script in the same directory as your punishments.json.
Replace YOUR_PROJECT and YOUR_SERVICE_ROLE_KEY with the values
from your Supabase project's Settings → API page.
Use the service role key (not the anon key) so the insert bypasses RLS.
// import-punishments.mjs
import { createClient } from '@supabase/supabase-js'
import { readFileSync } from 'fs'
const supabase = createClient(
'https://YOUR_PROJECT.supabase.co',
'YOUR_SERVICE_ROLE_KEY' // use service role key, not anon key
)
const raw = JSON.parse(readFileSync('./punishments.json', 'utf8'))
const records = raw.map(r => ({
id: r.id,
guild_id: r.guild_id,
offender: r.offender,
punisher: r.punisher ?? null,
reason: r.reason ?? null,
action: r.action?.toLowerCase() ?? null, // normalise Mute/Strike → mute/strike
timestamp: r.timestamp?.epoch_time
? new Date(r.timestamp.epoch_time * 1000).toISOString()
: null,
duration: r.duration != null ? parseInt(r.duration, 10) : null,
deleted: r.deleted ?? false,
removed_by: r.removed_by ?? null,
removed_reason: r.removed_reason ?? null,
resolve: r.resolve ?? null,
}))
const CHUNK_SIZE = 500
for (let i = 0; i < records.length; i += CHUNK_SIZE) {
const chunk = records.slice(i, i + CHUNK_SIZE)
const { error } = await supabase.from('punishments').insert(chunk)
if (error) {
console.error(`❌ Chunk ${Math.floor(i / CHUNK_SIZE) + 1} failed:`, error.message)
} else {
console.log(`✅ Chunk ${Math.floor(i / CHUNK_SIZE) + 1} inserted (${chunk.length} rows)`)
}
}
console.log(`\nDone! ${records.length} records processed.`)
Run the Script
Execute the import with Node.js:
node import-punishments.mjs
The script processes records in chunks of 500 to avoid payload limits. Each chunk logs a ✅ on success or ❌ with an error message on failure.
Ready to get started?
Full setup guide, command reference, and self-hosting instructions are in the docs.