Telegram: Trả lời/Điều khiển tự động dùng lua script

  • Cho telegram-cli chạy nền
  • Chặn sự kiện msg.in để chọn lọc các tin nhắn đến và trả lời dựa trên tập hợp các từ khóa có sẵn

Có vài cách để đọc tin nhắn đến. Ở đây chúng ta dùng ngôn ngữ kịch bản lua. Ngôn ngữ lua hỗ trợ regular expressioncsdl kém nên chỉ dùng cách này khi tập hợp từ khóa nhỏ (vài trăm ngàn)

Các từ khóa, thí dụ, dùng để chạy lệnh trên RPi (các lệnh được phép chạy) được ghi trong file command.csv

cd
cp
df
du
id
ln
ls
mv
rm
cat
man
sed
who
date
echo
exit
find
free
grep
kill
ping
read
size
stat
tail
chgrp
chmod
chown
mkdir
pidof
rmdir
uname
md5sum
pstree
reboot
uptime
whatis
whoami
killall
whereis
hostname
shutdown

Chúng ta không sắp xếp theo tên, mà trước hết sắp xếp theo độ dài. Để biết một lệnh nằm trong tin nhắn có được phép thi hành hay không, chúng ta tra theo danh sách này theo cách tuần tự nếu danh sách nhỏ, hay dùng binary search theo chiều dài từ khóa trước nếu danh sách lớn.

Tương tự, chúng ta tạo danh sách khác dùng như từ điển hay danh sách hàng hóa và giá bán, đặt tên là keyword.csv

"Macbook Pro Retina 13.3inch MF840", "27.999.000"
"Macbook Pro Retina 15.4inch MGXA2", "30.339.000"
"Macbook Pro Retina 13.3inch MF840", "26.899.000"

Có thể chia thành nhiều cột để dễ nhận diện từ khóa

Tiếp theo, chúng ta tạo lua script tên là action.lua, thay thế cho script xử lý mặc định của telegram

keyfile = '/home/pi/keyword.csv'
cmdfile = '/home/pi/command.csv'
keywords = {}
commands = {}
--++ cắt khoảng trắng 2 bên chuỗi ++--
function trim(str)
  str = tostring(str or '')
  return str:gsub("^%s*(.-)%s*$", "%1")
end
--++ đọc các field từ một dòng csv ++--
function csvsplit (str, sep)
  sep = '%s*'..sep..'%s*'
  str = trim(str)
  fields = {}
  fieldstart = 1
  if str then
    repeat
      _, i = str:find ('^%s*"', fieldstart)
      if i then -- field bọc trong ""
        fieldstart = i
        repeat -- tìm field
          i, e = str:find ('"("?)', i+1)
        until i == e
        local f = str:sub (fieldstart+1, i-1)
        table.insert (fields, (f:gsub ('""', '"')))
        _, i = str:find (sep, i)
        if i ~= nil then
          fieldstart = i+1
        else
          break
        end
      else -- field không bọc trong ""
        local _, i = str:find (sep, fieldstart)
        if i ~= nil then
          table.insert (fields, str:sub (fieldstart, i-1))
          fieldstart = i + 1
        else
          table.insert (fields, str:sub (fieldstart, str:len()))
          break
        end
      end
    until fieldstart > str:len()
  end
  return fields
end
--++ đọc csv file vào table ++--
function csvLoad(path, sep)
  local csvTable = {}
  local file = assert (io.open(path, "r"))
  for line in file:lines() do
    fields = csvsplit (line, sep)
    if next (fields) ~= nil then table.insert (csvTable, fields) end
  end
  file:close()
  return csvTable
end
--++ đọc các file csv vào table ++--
keywords = csvLoad (keyfile, ',')
commands = csvLoad (cmdfile, ',')
--++ hàm xử lý sự kiện tin nhắn đến ++--
function on_msg_receive (msg)
  vusr = {'Ly_Anh-Tuan, Test_User'}
  --user có trong danh sách (hợp lệ)?--
  function validuser(usr)
    found = false
    for i=1,#vusr do
      if vusr[i] == usr then
        found = true
        break
      end
    end
    return found
  end
  --chỉ xử lý tin đến và từ user trong danh sách--
  if msg.out or not validuser(msg.from.print_name) then
    return
  end
  ---------------------------
  function iskeyword(str, sFind)
    str = tostring (str or '')
    return str:gsub('(.+)', ' %1 '):find('([%s%p])('..sFind..')([%s%p])')
  end
  ---------------------------
  function findkeyword (str)
  -- binarysearch keywords theo length, rồi theo word
  -- tìm tuần tự nếu csdl nhỏ
    str = tostring (str or '')
    result = ''
    for i=1,#keywords do
      local kw = keywords[i][1]
      local c = kw:sub(1,1)
      local tmp = '['..string.lower(c)..','..string.upper(c)..']'..kw:sub(2)
      if iskeyword(str, tmp) ~= nil then
        result = result..'['..kw.."\n"..keywords[i][2].."]\n"
      end
    end
    return result
  end
  --Thực hiện lệnh trên RPi và lấy kết quả trả về
  function osrun(cmd)
    local tmp = os.tmpname ()
    os.execute (cmd .. ' > ' .. tmp .. ' 2>&1')
    local f = assert (io.open (tmp, 'rb'))
    local result = f:read ('*all')
    f:close ()
    os.remove (tmp)
    return tostring (result or '')
  end
  ---Tìm từ khóa/lệnh và thực hiện---
  local reply = ''
  local tmp = findkeyword (msg.text)
  if tmp ~= '' then
    reply = "###### keywords ######\n" .. tmp .. "\n"
  end
  local tmp = findcommand (msg.text)
  if tmp ~= '' then
    reply = reply .. "###### commands ######\n" .. tmp .. "\n"
  end
  reply = reply .. "###### messages ######\n" .. msg.text
  ---gởi tin nhắn trả lời---
  send_msg (msg.from.print_name, reply, ok_cb, false)
end
--Các hàm đáp ứng các sự kiện khác của telegram, không cần can thiệp--
--++++++++++++++++++++++++++++++++--
function ok_cb()
end
--++++++++++++++++++++++++++++++++--
function on_our_id (id)
end
--++++++++++++++++++++++++++++++++--
function on_secret_chat_created (peer)
end
--++++++++++++++++++++++++++++++++--
function on_user_update (user)
end
--++++++++++++++++++++++++++++++++--
function on_chat_update (user)
end
--++++++++++++++++++++++++++++++++--
function on_get_difference_end ()
end
--++++++++++++++++++++++++++++++++--
function on_binlog_replay_end ()
end

Cuối cùng, trong dòng lệnh gọi chạy telegram-cli phải có tham số -s /path/to/script.lua

telegram-cli -W -s /home/pi/action.cript

Sau đó các tin nhắn đến đều được xử lý và trả lời, qui ước

  1. Từ khóa sẽ được tự động dò tìm, không cần đánh dấu
  2. Lệnh phải đặt trong [lệnh]

Thí dụ về tin trả lời khi gởi tin có nội dung [ls /]

###### commands ######
[ls /
bin
boot
boot.bak
dev
etc
home
lib
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
]
###### messages ######
[ls /]

Chú thích

  • Bất cứ ai biết username của mình đều có thể nhắn tin telegram, vì vậy phải có danh sách user có quyền nhắn tin và lọc user trước
  • Với lệnh sudo gởi qua telegram, user telegramd phải được thêm vào /etc/sudoers mới có thể thi hành lệnh
  • Có thể cho telegram-cli chạy nền khi khởi động để xử lý tin
  • telegram-cli phải được gọi sau khi quá trình khởi động hoàn tất
@reboot sleep 30 && telegram-daemon start
  • Để có thể dùng Regular Expression một cách linh hoat, dễ dàng hơn, cần phải cài đặt thêm lua-rex-pcre
apt-get install lua-rex-pcre

Khi đó dòng lệnh regular expression tương tự như sau

rex = require "rex_pcre"
print (rex.match ("Hôm nay tôi đi học", "TÔI ĐI HỌC", 1+2048)) --> tôi đi học

Leave a Comment

Filed under Software

Leave a Reply