2022-04-11 06:06:32 +00:00
#
2022-04-11 10:40:24 +00:00
# Copyright © 2022 Maestro Creativescape
2022-04-11 06:06:32 +00:00
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
2022-04-09 17:45:02 +00:00
import telebot
2022-04-10 17:54:48 +00:00
import std / [ asyncdispatch , logging , options , strutils , random , with , os , tables , times , sequtils ]
2022-04-09 17:45:02 +00:00
import norm / [ model , sqlite ]
2022-04-07 17:41:36 +00:00
2022-04-11 06:15:19 +00:00
# Logging Level
2022-04-11 07:19:45 +00:00
var L = newConsoleLogger ( levelThreshold = lvlError , fmtStr = " $levelname , [ $time ] " )
2022-04-11 04:14:57 +00:00
addHandler ( L )
2022-04-11 06:15:19 +00:00
# Custom Types Defined to handle tables on norm
2022-04-07 17:41:36 +00:00
type
2022-04-09 15:18:41 +00:00
CensoredData * = ref object of Model
2022-04-07 17:41:36 +00:00
ftype * : string
2022-04-09 14:30:05 +00:00
fhash * : string
2022-04-07 17:41:36 +00:00
fileid * : string
2022-04-11 04:06:04 +00:00
time * : float
2022-04-07 17:41:36 +00:00
caption * : string
2022-04-09 17:45:02 +00:00
type
BannedUsers * = ref object of Model
2022-04-10 17:54:48 +00:00
userid * : int64
bantime * : float
bantype * : string
2022-04-11 06:15:19 +00:00
# Hashmaps for RateLimiting and working around telegram's album issue
2022-04-10 17:54:48 +00:00
var RateLimiter = initTable [ int64 , seq [ float ] ] ( )
2022-04-11 02:46:47 +00:00
var GroupMedia = initTable [ int , string ] ( )
2022-04-11 06:15:19 +00:00
# Bot Admins, is comma-separated variable
2022-04-10 17:54:48 +00:00
let AdminID = getEnv ( " ADMIN_ID " ) . split ( " , " )
2022-04-09 17:45:02 +00:00
2022-04-11 04:14:57 +00:00
let dbConn * = getDb ( )
2022-04-11 06:15:19 +00:00
# Functions to assist adding/deleting entries from tables
2022-04-11 04:20:07 +00:00
func NewCensoredData * ( ftype = " " ; fhash = " " ; fileid = " " ; time = 0 .0 ; caption = " " ) :
CensoredData = CensoredData ( ftype : ftype , fhash : fhash , fileid : fileid , time : time , caption : caption )
2022-04-07 17:41:36 +00:00
2022-04-10 17:54:48 +00:00
func NewBannedUsers * ( userid = int64 ( 0 ) , bantime = 0 .0 , bantype = " " ) :
BannedUsers = BannedUsers ( userid : userid , bantime : bantime , bantype : bantype )
2022-04-09 17:45:02 +00:00
2022-04-11 06:15:19 +00:00
# Create tables if they dont exist
2022-04-11 04:14:57 +00:00
dbConn . createTables ( NewCensoredData ( ) )
dbConn . createTables ( NewBannedUsers ( ) )
2022-04-07 17:41:36 +00:00
2022-04-11 06:15:19 +00:00
# Ratelimits a user if there is more than 20 timestamps within 60 secs.
# Resets their ratelimit counter if 60s has passed since first timestamp
2022-04-10 17:54:48 +00:00
proc ManageRateLimit ( ) : void =
for i in RateLimiter . keys ( ) . toSeq ( ) :
if RateLimiter [ i ] [ ^ 1 ] - RateLimiter [ i ] [ 0 ] > = 60 :
RateLimiter . del ( i )
elif len ( RateLimiter [ i ] ) > = 20 :
var BannedUser = NewBannedUsers ( i , epochTime ( ) , " auto " )
RateLimiter . del ( i )
with dbConn :
insert BannedUser
if dbConn . exists ( BannedUsers , " bantype = ? and ? - bantime >= 1800 " , " auto " , epochTime ( ) ) :
var TempData = @ [ NewBannedUsers ( ) ]
dbConn . select ( TempData , " bantype = ? and ? - bantime >= 1800 " , " auto " , epochTime ( ) )
for i in TempData :
var e = i
dbConn . delete ( e )
2022-04-11 06:15:19 +00:00
# Cleanup old data to save space
# Deletes old data after 6 months of entry
2022-04-11 04:14:57 +00:00
proc OldDataCleanup ( ) : void =
if dbConn . exists ( CensoredData , " ? - time >= 15780000 " , epochTime ( ) ) :
var TempData = @ [ NewCensoredData ( ) ]
dbConn . select ( TempData , " ? - time >= 15780000 " , epochTime ( ) )
for i in TempData :
var e = i
dbConn . delete ( e )
2022-04-07 10:51:02 +00:00
2022-04-11 06:15:19 +00:00
# Generates a 6-character magic that will be used to identify the file
2022-04-07 11:45:56 +00:00
proc generate_hash ( ) : string =
result = newString ( 7 )
const charset = { ' a ' .. ' z ' , ' A ' .. ' Z ' , ' 0 ' .. ' 9 ' }
for i in 0 .. 6 :
result [ i ] = sample ( charset )
return result
2022-04-11 09:38:42 +00:00
# Start command handler
proc startHandler ( b : Telebot , c : Command ) : Future [ bool ] {. gcsafe , async . } =
let param = c . params
ManageRateLimit ( )
if not dbConn . exists ( BannedUsers , " userid = ? " , c . message . chat . id ) :
# Update rate-limit counter
if RateLimiter . contains ( c . message . chat . id ) :
RateLimiter [ c . message . chat . id ] . insert ( epochTime ( ) )
else :
RateLimiter [ c . message . chat . id ] = @ [ epochTime ( ) ]
if param = = " " :
discard await b . sendMessage ( c . message . chat . id , " Hey, To create a censored post, you can share any album, video, photo, gif, sticker, etc. The messages could then be forwarded to any chat for them to view " )
else :
if not dbConn . exists ( CensoredData , " fhash = ? " , param ) :
discard await b . sendMessage ( c . message . chat . id , " Media does not exist on database, ask the sender to censor this again! " )
else :
var TempData = @ [ NewCensoredData ( ) ]
dbConn . select ( TempData , " fhash = ? " , param )
if len ( TempData ) > 1 :
var inputmedia = newSeq [ InputMediaPhoto ] ( )
for i in TempData :
if i . caption ! = " " :
inputmedia . insert ( InputMediaPhoto ( kind : i . ftype , media : i . fileid , caption : some ( i . caption ) ) )
else :
inputmedia . insert ( InputMediaPhoto ( kind : i . ftype , media : i . fileid ) )
discard await b . sendMediaGroup ( $ c . message . chat . id , media = inputmedia )
else :
if TempData [ 0 ] . ftype = = " photo " :
discard await b . sendPhoto ( c . message . chat . id , TempData [ 0 ] . fileid , TempData [ 0 ] . caption )
elif TempData [ 0 ] . ftype = = " document " :
discard await b . sendDocument ( c . message . chat . id , TempData [ 0 ] . fileid , TempData [ 0 ] . caption )
elif TempData [ 0 ] . ftype = = " video " :
discard await b . sendVideo ( c . message . chat . id , TempData [ 0 ] . fileid , caption = TempData [ 0 ] . caption )
elif TempData [ 0 ] . ftype = = " videoNote " :
discard await b . sendVideoNote ( c . message . chat . id , TempData [ 0 ] . fileid )
elif TempData [ 0 ] . ftype = = " animation " :
discard await b . sendAnimation ( c . message . chat . id , TempData [ 0 ] . fileid , caption = TempData [ 0 ] . caption )
elif TempData [ 0 ] . ftype = = " sticker " :
discard await b . sendSticker ( c . message . chat . id , TempData [ 0 ] . fileid )
2022-04-11 09:54:22 +00:00
# UnBan Handler
proc unbanHandler ( b : Telebot , c : Command ) : Future [ bool ] {. gcsafe , async . } =
if $ c . message . chat . id in AdminID :
let user = c . params
if dbConn . exists ( BannedUsers , " userid = ? " , int64 ( parseInt ( user ) ) ) :
var TempData = @ [ NewBannedUsers ( ) ]
dbConn . select ( TempData , " userid = ? " , int64 ( parseInt ( user ) ) )
for i in TempData :
var e = i
dbConn . delete ( e )
discard await b . sendMessage ( c . message . chat . id , " Unbanned! " )
# ban Handler
2022-04-11 09:38:42 +00:00
proc banHandler ( b : Telebot , c : Command ) : Future [ bool ] {. gcsafe , async . } =
if $ c . message . chat . id in AdminID :
let user = c . params
var BannedUser = NewBannedUsers ( int64 ( parseInt ( user ) ) , epochTime ( ) , " permanent " )
with dbConn :
insert BannedUser
discard await b . sendMessage ( c . message . chat . id , " Banned! " )
2022-04-11 06:15:19 +00:00
# Main update handler
2022-04-09 14:26:17 +00:00
proc updateHandler ( b : Telebot , u : Update ) : Future [ bool ] {. async , gcsafe . } =
2022-04-09 15:41:01 +00:00
let response = u . message . get
2022-04-11 06:15:19 +00:00
# Refresh rate-limits
2022-04-10 17:54:48 +00:00
ManageRateLimit ( )
2022-04-11 06:15:19 +00:00
# Dont bother about rate-limited/banned users
2022-04-09 17:45:02 +00:00
if not dbConn . exists ( BannedUsers , " userid = ? " , response . chat . id ) :
2022-04-11 06:15:19 +00:00
# Update rate-limit counter
2022-04-10 17:54:48 +00:00
if RateLimiter . contains ( response . chat . id ) :
RateLimiter [ response . chat . id ] . insert ( epochTime ( ) )
else :
RateLimiter [ response . chat . id ] = @ [ epochTime ( ) ]
2022-04-11 09:38:42 +00:00
if not response . text . isSome :
2022-04-09 17:45:02 +00:00
var fileid = " "
var ftype = " "
var fcaption = " "
if response . caption . isSome :
fcaption = response . caption . get
if response . document . isSome :
fileid = response . document . get . fileId
ftype = " document "
elif response . video . isSome :
fileid = response . video . get . fileId
ftype = " video "
elif response . videoNote . isSome :
fileid = response . videoNote . get . fileId
2022-04-11 02:46:47 +00:00
ftype = " videoNote "
2022-04-09 17:45:02 +00:00
elif response . animation . isSome :
fileid = response . animation . get . fileId
ftype = " animation "
elif response . photo . isSome :
fileid = response . photo . get [ 0 ] . fileId
ftype = " photo "
elif response . sticker . isSome :
fileid = response . sticker . get . fileId
ftype = " sticker "
2022-04-11 06:15:19 +00:00
# Workaround for groupmedia using hashtables
# Telegram is sending multiple updates, instead of 1, so just workaround it
2022-04-11 02:46:47 +00:00
if response . mediaGroupId . isSome :
if parseInt ( response . mediaGroupId . get ) notin GroupMedia . keys ( ) . toSeq ( ) :
let filehash = generate_hash ( )
GroupMedia [ parseInt ( response . mediaGroupId . get ) ] = filehash
2022-04-11 04:20:07 +00:00
var CensoredRow = NewCensoredData ( ftype , filehash , fileid , epochTime ( ) , fcaption )
2022-04-11 02:46:47 +00:00
with dbConn :
insert CensoredRow
2022-04-17 09:30:51 +00:00
var replybutton = InlineKeyboardButton ( text : " Share " , switchInlineQuery : some ( filehash ) )
let replymark = newInlineKeyboardMarkup ( @ [ replybutton ] )
discard await b . sendMessage ( response . chat . id , " *Censored " & capitalizeAscii ( ftype ) & " * " , replyMarkup = replymark , parseMode = " Markdown " )
2022-04-11 02:46:47 +00:00
else :
let filehash = GroupMedia [ parseInt ( response . mediaGroupId . get ) ]
2022-04-11 04:20:07 +00:00
var CensoredRow = NewCensoredData ( ftype , filehash , fileid , epochTime ( ) , fcaption )
2022-04-11 02:46:47 +00:00
with dbConn :
insert CensoredRow
else :
let filehash = generate_hash ( )
2022-04-11 04:20:07 +00:00
var CensoredRow = NewCensoredData ( ftype , filehash , fileid , epochTime ( ) , fcaption )
2022-04-11 02:46:47 +00:00
with dbConn :
insert CensoredRow
2022-04-17 09:30:51 +00:00
var replybutton = InlineKeyboardButton ( text : " Share " , switchInlineQuery : some ( filehash ) )
let replymark = newInlineKeyboardMarkup ( @ [ replybutton ] )
discard await b . sendMessage ( response . chat . id , " *Censored " & capitalizeAscii ( ftype ) & " * " , replyMarkup = replymark , parseMode = " Markdown " )
2022-04-11 04:14:57 +00:00
OldDataCleanup ( )
2022-04-07 10:51:02 +00:00
2022-04-11 06:15:19 +00:00
# Start the bot
2022-04-10 17:54:48 +00:00
let bot = newTeleBot ( getEnv ( " TELEGRAM_TOKEN " ) )
2022-04-11 07:19:45 +00:00
echo " ********************* "
echo " CensorBot is running! "
echo " ********************* "
2022-04-11 09:38:42 +00:00
var commands = @ [
BotCommand ( command : " start " , description : " Start the bot! " )
]
discard waitFor bot . setMyCommands ( commands )
2022-04-07 10:51:02 +00:00
bot . onUpdate ( updateHandler )
2022-04-11 09:38:42 +00:00
bot . onCommand ( " start " , startHandler )
bot . onCommand ( " ban " , banHandler )
2022-04-11 09:54:22 +00:00
bot . onCommand ( " unban " , unbanHandler )
2022-04-11 12:45:59 +00:00
if getEnv ( " HOOK_DOMAIN " ) ! = " " :
bot . startWebhook ( getEnv ( " HOOK_SECRET " ) , getEnv ( " HOOK_DOMAIN " ) & " / " & getEnv ( " HOOK_SECRET " ) )
else :
bot . poll ( timeout = 300 )