(in-package :cl-user) (defpackage chatikbot (:use :cl :alexandria) (:import-from :chatikbot.db :with-db :db-init :db-execute :db-select :load-settings) (:import-from :chatikbot.utils :*bot-name* :*admins* :aget :format-ts :run-hooks :loop-with-error-backoff) (:import-from :chatikbot.telegram :*telegram-token* :telegram-get-me :telegram-set-webhook :telegram-send-message) (:import-from :chatikbot.secrets :*secret-ring* :*secret-pass-store* :*secret-pass-bin*) (:import-from :chatikbot.macros :defcron) (:import-from :chatikbot.bot :process-updates :*bot-user-id*) (:import-from :chatikbot.server :*web-path* :*web-iface* :*web-port*) (:export :start)) (in-package :chatikbot) (defvar *plugins* nil "list of enabled plugins.") (defun plugins-db-init () (db-execute "create table if not exists plugins (name)") (db-execute "create unique index if not exists plugins_name_unique on plugins (name)")) (defun enable-plugin (name) (load (merge-pathnames (format nil "plugins/~A.lisp" name) (asdf:component-pathname (asdf:find-system '#:chatikbot)))) (db-execute "replace into plugins (name) values (?)" name) (push name *plugins*)) (defun disable-plugin (name) (db-execute "delete from plugins where name = ?" name) (setf *plugins* (delete name *plugins* :test #'equal))) (eval-when (:load-toplevel :execute) ;; Load config file (alexandria:when-let (file (probe-file "config.lisp")) (load file)) ;; Init database (db-init) ;; Load plugins (plugins-db-init) (setf *plugins* (flatten (db-select "select name from plugins"))) (dolist (plugin *plugins*) (log:info "Loading" plugin) (handler-case (load (merge-pathnames (format nil "plugins/~A.lisp" plugin) (asdf:component-pathname (asdf:find-system '#:chatikbot)))) (error (e) (log:error e)))) ;; Load settings (load-settings) ;; Init plugin's database (with-db (db) (run-hooks :db-init))) (defcron process-watchdog () (close (open (merge-pathnames ".watchdog" (asdf:component-pathname (asdf:find-system '#:chatikbot))) :direction :output :if-exists :supersede :if-does-not-exist :create))) (defun cleanup () ;; Clear prev threads (mapc #'trivial-timers:unschedule-timer (trivial-timers:list-all-timers)) (let ((old-updates (find "process-updates" (bordeaux-threads:all-threads) :key #'bordeaux-threads:thread-name :test #'equal))) (when old-updates (bordeaux-threads:destroy-thread old-updates)))) (defun start () ;; Test telegram token (setf *bot-name* (concatenate 'string "@" (aget "username" (telegram-get-me))) *bot-user-id* (parse-integer *telegram-token* :end (position #\: *telegram-token*))) (cleanup) ;; Run 'starting' hooks to set up schedules (run-hooks :starting) (if *web-path* ;; If *web-path* is set, use webhooks (telegram-set-webhook (format nil "~A/~A" *web-path* *telegram-token*)) ;; else start getUpdates thread (progn (telegram-set-webhook "") ;; Disable webhooks if present (bordeaux-threads:make-thread (lambda () (loop-with-error-backoff #'process-updates)) :name "process-updates"))) ;; Notify admins (ignore-errors (telegram-send-message (car *admins*) (format nil "~A started at ~A" *bot-name* (format-ts (local-time:now))))))