Tutorial: Karma Bot
A group reputation engine: members reply +rep to award karma, the bot keeps a shared ledger, and a weekly schedule broadcasts the leaderboard. Based on the karma-rep-bot.botgami blueprint.
What you'll learnâ
- A single
on message [text]handler that does double duty - Reading the reply target (
message.reply_to_message.from) - A
shared.*ledger with the upsert pattern - Anti-abuse cooldowns and a scheduled broadcast
Step 1: Stateâ
bot KarmaBot {
meta { name: "Karma Bot" slug: "karma-bot" icon: "â" }
// Cross-user ledger â one entry per known member.
shared ledger: Array<{ uid: Number, name: String, rep: Number }> = []
shared group_chat_id: Number = 0
// Per-user cooldown: which targets you already repped today.
user rep_cooldowns: Array<{ uid: Number, day: String }> = []
global admin_id: Number = 0
}
Step 2: The +rep handlerâ
A single on message [text] handler captures the group id and, when the message starts with +rep and replies to someone, awards reputation. Using one handler avoids triggers racing each other.
flow on_text on message [text] {
set { shared.group_chat_id = user.chat.id }
when str.startsWith(str.trim(message.text), "+rep") {
set { flow.target = message.reply_to_message.from }
when types.isNil(flow.target) {
send.text("âŠī¸ Reply to a member's message with +rep to award them.")
} else {
set { flow.target_uid = flow.target.id }
set { flow.target_name = flow.target.first_name }
when flow.target_uid == user.id {
send.text("đ
You can't rep yourself.")
} else {
await award_rep(target_uid: flow.target_uid, target_name: flow.target_name)
}
}
}
}
Step 3: Award with a cooldownâ
The award subflow enforces "once per target per day," then upserts the target's rep.
subflow award_rep(target_uid: Number, target_name: String) {
set { flow.today = time.date() }
set { flow.prior = array.find(user.rep_cooldowns, "uid", target_uid) }
when (!types.isNil(flow.prior)) && (flow.prior.day == flow.today) {
send.text(`âŗ You already repped ${target_name} today.`)
} else {
// Upsert target's rep (+1).
set { flow.t = array.find(shared.ledger, "uid", target_uid) }
set { flow.new_rep = (types.isNil(flow.t) ? 0 : flow.t.rep) + 1 }
set {
shared.ledger = array.reject(shared.ledger, "uid", target_uid)
shared.ledger << [{ uid: target_uid, name: target_name, rep: flow.new_rep }]
}
// Record the cooldown.
set {
user.rep_cooldowns = array.reject(user.rep_cooldowns, "uid", target_uid)
user.rep_cooldowns << [{ uid: target_uid, day: flow.today }]
}
send.text(`â ${user.first_name} gave +rep to ${target_name}! Total: ${flow.new_rep}`)
}
return {}
}
Step 4: Leaderboard commandâ
flow top on command "/top" {
when util.len(shared.ledger) == 0 {
send.text("đ No reputation yet. Reply +rep to someone to start the board!")
} else {
set { flow.ranked = array.slice(array.sortBy(shared.ledger, "rep", true), 0, 10) }
set { flow.body = "đ Reputation Leaderboard\n\n" }
foreach e, i in flow.ranked {
set { flow.pos = i + 1 }
set { flow.body = flow.body + `${flow.pos}. ${e.name} â ${e.rep} rep\n` }
}
send.text(flow.body)
}
}
Step 5: Weekly broadcastâ
Every Monday at 09:00, post the board to the group (using the captured chat id).
flow weekly on schedule "0 9 * * 1" {
when shared.group_chat_id != 0 {
set { flow.ranked = array.slice(array.sortBy(shared.ledger, "rep", true), 0, 10) }
when util.len(flow.ranked) > 0 {
set { flow.body = "đ Weekly Reputation Leaderboard\n\n" }
foreach e, i in flow.ranked {
set { flow.pos = i + 1 }
set { flow.body = flow.body + `${flow.pos}. ${e.name} â ${e.rep} rep\n` }
}
send.text(flow.body, chat_id: shared.group_chat_id)
}
}
}