Jabber-бот на базе Яндекса

Некоторое время назад написал я программу-бота, которой можно управлять через XMPP-протокол (Jabber). Были у нее кое-какие-недостатки, из которых главный — требование запущенного на том же хосте Jabber-сервера.

Оценив ситуацию свежим взглядом решил, что держать свой сервер вовсе необязательно: кругом полно бесплатных. Сначала «переселил» бота на GoogleTalk. Всё прекрасно, управлять можно и из браузера, и из любого IM-клиента с поддержкой Jabber. А вот с мобильного телефона нельзя. Ну нет у Google нормального клиента для сервиса GTalk. Ну что-ж, тогда Яндекс. У этих ребят мобильный клиент давно написан: им можно и почту посмотреть, и пообЧАТься. Кроме того, из браузера чат тоже можно вести.

Сам бот тоже поумнел. Теперь он не только выполняет простые bash-команды, но и интерпретирует синтаксические конструкции на языках Java/Groovy, а также… переключает телевизионные каналы (да, такая вот Jabber-«лентяйка» для телевизора ).



От злоумышленников бот защищен тем, что признает только команды, переданные с определенного аккаунта: чтобы управлять им, нужно сначала захватить мою учетную запись на Яндексе.

Привожу основную часть кода. Остальное — по запросу, если кто заинтересуется.

package ru.yababay.chat.server;

import java.io.File;
import java.io.FileInputStream;

import java.lang.reflect.Method;

import java.util.Enumeration;
import java.util.Properties;
import java.util.HashMap;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;

import net.sf.lab3f.groovy.Groovyable;
import net.sf.lab3f.util.TuttiFruttiable;

public class Main implements ChatManagerListener, MessageListener {

 private XMPPConnection connection;
 private Properties props = new Properties();
 private ChatManagerListener chatMan;
 private boolean hasGreeting;
 private Groovyable groovy;
 private Object groovyObj;
 private TuttiFruttiable tuttiFrutti;
 private HashMap <String, java.lang.reflect.Method> methods = new HashMap <String, java.lang.reflect.Method> ();
 private final String row = "\n------------------\n";
 
 public void processMessage(Chat cht, Message msg){	 
  try{	 
   String s = msg.getBody().trim();
   int n = s.indexOf('\n');
   if(n > 0 && s.substring(0, n).indexOf("groovy") > -1){
    s = s.substring(n).trim();	   
    cht.sendMessage('\n' + props.getProperty("botReplay1") + row + groovy.eval(s).toString() + row + props.getProperty("botReplay2"));
    return;
   }	   
   n = s.indexOf(':');
   if(n > 0 && methods.get(s.substring(0, n).trim()) != null){
    Method m = methods.get(s.substring(0, n).trim());	   
    Object obj = m.invoke(groovyObj, s.substring(n + 1).trim());
    cht.sendMessage('\n' + props.getProperty("botReplay1") + row + obj.toString().trim() + row + props.getProperty("botReplay2"));
    return;
   }	   
   else cht.sendMessage(props.getProperty("botReplayr3"));
   cht.sendMessage(props.getProperty("botReplay2"));
  }   
  catch(Exception ex){System.out.println(ex);}
 }	 

 public void chatCreated(Chat cht, boolean local){
  String s = cht.getParticipant();
  System.out.println(s);
  if(s.indexOf('/') > 0)s = s.substring(0, s.lastIndexOf('/'));
  if(!s.equals(props.getProperty("adminJidYa")))return;
  cht.addMessageListener(this);
  try{
   if(!hasGreeting){cht.sendMessage(props.getProperty("botGreeting"));hasGreeting = true;}
  }
  catch(XMPPException ex){} 
 }	 

 public final void start() throws Exception {
  props.load(new FileInputStream("conf/jabberbot.properties"));	 
  groovyObj = groovy.eval(new File(props.getProperty("path2Script")));
  Class grClass = groovyObj.getClass();
  Method[] meths = grClass.getMethods();
  for(Method m : meths)methods.put(m.getName(), m);
  chatMan = this;	 
  ping.start();
  hasGreeting = false;
  System.out.println("[INFO] Jabber bot started.");
 }

 public final void stop() throws Exception {
  ping.interrupt();	 
  if(connection != null)connection.disconnect();	 
 }

 private class YandexTalkConnection extends XMPPConnection {
  public YandexTalkConnection() throws XMPPException {
   super(new ConnectionConfiguration("xmpp.yandex.ru", 5222, "ya.ru"));	  
  }	  
 }

 private class GoogleTalkConnection extends XMPPConnection {
  public GoogleTalkConnection() throws XMPPException {
   super(new ConnectionConfiguration("talk.google.com", 5222, "gmail.com"));	  
  }	  
 }

 private Thread ping = new Thread(new Runnable(){
  public void run(){
   while(true){
    try{
     if(connection != null && connection.isConnected()){Thread.sleep(300000);return;}	    
     connection = new YandexTalkConnection();
//     System.setProperty("smack.debugEnabled", "true");
//     connection.DEBUG_ENABLED = true;
     SASLAuthentication.supportSASLMechanism("PLAIN");
     connection.connect();
     connection.login(props.getProperty("botJidYa"), props.getProperty("botPasswdYa"), "");
     ChatManager chMan = connection.getChatManager();
     chMan.addChatListener(chatMan);
    }
    catch(Exception ex){}    
   }	   
  }	  
 }); 
}

Комментарии (19)

RSS свернуть / развернуть
+
0
Классная штука! Яндексовский клиент — это который Яндекс почта чтоли?
avatar

Sergei_T

  • 12 июля 2011, 18:02
+
0
Кстати, вполне можно использовать на мобильном в качестве jabber клиента, например, ICQ Mobile или nimbuzz (тут вообще можно и со страницы управлять и т.п.)
avatar

Sergei_T

  • 12 июля 2011, 18:04
+
0
Да, чтобы яндексовским джаббер-сервером пользоваться, нужнен аккаунт на Яндекс-почте + зарегистрироваться в их сервисе Я.ру (типа социальная сеть).
avatar

yababay

  • 12 июля 2011, 18:18
+
0
Блин, все кому не лень штампуют свои клиенты и социальные сети
avatar

Sergei_T

  • 12 июля 2011, 18:26
+
0
На ya.ru не обязательно, есть почта-есть jid.
avatar

illuthion

  • 14 июля 2011, 02:45
+
0
Я в качестве курсового делал Jabber бота на bash, но там довольно страшно вышло: Бот сканировал историю сообщений из freetalk, искал команды, потом чего-то делал и высылал результат по sendxmpp) Тот еще говнокод…
А вообще начинал писать его на чистом ruby но в итоге начались странные проблемы с тем, что не все сервера принимали отправленные сообщения…
avatar

illuthion

  • 13 июля 2011, 20:17
+
0
На bash ?!

XMPP-протокол довольно прост и понятен. Достаточно установить отладочную консоль в Pidgin или Psi — и детали общения клиента и сервера станут ясны. Но есть одно но: TLS-аутентификация. Вот она-то и не позволяет, например, с легкостью написать простое мобильное приложение для общения с тем-же Jabber-ботом. Есть, например, мобильный клиент MGtalk. Заглянул в исходный код — там ужас. Да и яндексовский мобильный клиент работает очень нестабильно: постоянно переустанавливает соединение и даже отправляет телефон в аут (внезапно экран гаснет и телефон выключается).
avatar

yababay

  • 13 июля 2011, 20:34
+
0
протокол и в правду легкий, с XML-консолью я одно время много игрался. А насчет TLS в ruby все просто было, при авторизации надо было указать использование шифрования и дополнительно задать порт, насчет других языков не знаю…

Но писал действительно на bash, уж больно он мне нравится своей простотой и адекватностью
avatar

illuthion

  • 13 июля 2011, 20:37
+
0
На bash? Интересно.
avatar

Sergei_T

  • 13 июля 2011, 21:10
+
0
Ночью выложу код если со стыда не помру)
avatar

illuthion

  • 13 июля 2011, 21:12
+
0
Не боись, все свои, ждем с нетерпением.
avatar

yababay

  • 13 июля 2011, 23:27
+
0
Ну собственно все довольно сырое и жутко-странно работающее, может кто доведет до ума на трезвую голову.

Теория:
Для работы бот использует freetalk и sendxmpp, соответственно и первое и второе должно быть настроено на JID бота. freetalk запускается перед запуском бота.
Бот(inc) запускается с 2 параметрами: JID бота и JID администратора(того от кого будет принимать команды).
После запуска идет бесконечный цикл на чтение последней стройки из истории freetalk и пределение новая это строка или нет. Если строка новая то JID и содержание передаются на управление другому скрипту(out) который отрезает от содержания команду а все остальное использует как параметры(было нужно для создания заметок и управления торрентами). Собственно потом команда уходит в case и идет выборка действия, ответ отсылается пользователю по sendxmpp. Разделение на 2 скрипта было сделано для того что-бы можно было править список команд не перезапуская бот. В принципе ничто не мешает заставить бота отсылать присланное сообщение в консоль и возвращать результат.

Код:
inc
#!/bin/bash

bot=$1
admin=$2

str=`tail -n1 ~/.freetalk/history/$1/$2`

while [ true ];do
str1=`tail -n1 ~/.freetalk/history/$1/$2`
if [ "$str1" != "$str" ]; then
 mes=${str1#*"] "}
 echo $mes
 ./out $admin $mes
 fi
str=$str1
done


out
#!/bin/bash

in=1
for a in "$@"; do
	case $in in
		1)admin=$a;;
		2)mes=$a;;
		*)par=$a;;
	esac
	let 'in+=1'
done

#Обработка команды
case "$mes" in

#Transmission
torrent_list)
	echo ''>t
	echo 'Активные torrent-сессии'>>t
	echo '---'
	transmission-remote localhost:4545 -l>>t
	sendxmpp  $admin -m t
;;
torrent_add)
	#wget -O t $par
	transmission-remote localhost:4545 --add $par
	echo 'Торрент добавлен' | sendxmpp $admin
;;
torrent_choise)
	transmission-remote localhost:4545 -t $par
;;
torrent_stop)
	transmission-remote localhost:4545 -S
;;
torrent_start)
	transmission-remote localhost:4545 -s
;;
torrent_on)
	transmission-daemon
	ps aux|grep [t]ransmission-daemon|sendxmpp $admin
;;
torrent_off)
	killall transmission-daemon
	ps aux|grep [t]ransmission-daemon|sendxmpp $admin
;;

#Диагностика
info_hdd)
	echo ''> t
	echo 'Состояние дисков' >> t
	echo '---' >> t
	df -h >> t
	sendxmpp $admin -m t
;;
info_mem)
	echo '' > t
	echo '---' >> t
	echo 'Состояние памяти' >> t
	free -kt >> t
	sendxmpp $admin -m t
;;
info_cpu)
	echo 'oops'|sendxmpp $admin
;;
info_net)
	echo '' > t
	echo 'Состояние сети' >> t
	echo '---' >> t
	echo "IP: $(wget -O - -q icanhazip.com)" >> t
	echo "Local IP: $(ifconfig eth0 |grep inet\ addr|cut -b21-33)" >> t
	echo '--'>> t
	echo 'Активные TCP соединения' >> t
	netstat | grep Recv-Q >> t
	netstat | grep tcp >> t
	sendxmpp $admin -m t 
;;
info_all)
	echo '' > t
	echo 'Информация о системе' >> t
	echo '---' >> t
	cat /proc/version >> t
	echo "Uptime: $(uptime|cut -d' ' -f2)" >> t
	echo '--' >> t
	grep MemTotal /proc/meminfo >> t
	grep MemFree /proc/meminfo >> t
	grep SwapTotal /proc/meminfo >> t
	grep SwapFree /proc/meminfo >> t
	echo '--' >> t
	grep platform /proc/cpuinfo >> t
	grep cpu /proc/cpuinfo >> t
	grep clock /proc/cpuinfo >> t
	echo '--' >> t
	df -h | grep Filesystem >> t
	df -h | grep md1 >> t
	df -h | grep sda4 >> t

	sendxmpp $admin -m t
;;

#Заметки
note_new)
	t=0
	while [ $t != 1 ]; do
	file=$((RANDOM%800+1))
	for i in $(ls); do
		if [ "$i" != "$file" ]
			then
			t=1
		fi
	done
	done

	echo "Заметка #$file:" >> notes/$file
	echo '' >> notes/$file
	echo "$par" >> notes/$file
	
	echo "Заметка #$file создана"|sendxmpp $admin
;;
note_show)
	sendxmpp $admin -m notes/$par
;;
note_list)
	echo ''>t
	echo 'Список заметок'>>t
	echo '---'>>t
	for i in $(ls notes/);do 
		echo "Заметка #$i">>t
	 	echo "$(tail -n1 notes/$i|cut -b1-30)...">>t
		echo ''>>t
	done
	sendxmpp $admin -m t
;;
note_del)
	rm notes/$par
	echo "Заметка $par удалена"|sendxmpp $admin
;;
help)
	sendxmpp $admin -m help
;;
info)
	sendxmpp $admin -m info
;;
*)
	sendxmpp $admin -m help
;;
esac


Заключение:
Минусов как видите масса, например демонизировать можно только через screen, думал довести до ума, но не нашел чем можно заменить freetalk, если только мисать отдельный скрипт на ruby/perl но тогда самого бота будет выгоднее переписать. В общем кому интересно можете покопать)
avatar

illuthion

  • 14 июля 2011, 03:01
+
0
Вот он, unix-way во всей своей красе.

Спасибо, я для себя парочку идей почерпнул. Такой камент просится в отдельный топик.
avatar

yababay

  • 14 июля 2011, 12:52
+
0
Если время будет я немного поправлю и выставлю как топик
avatar

illuthion

  • 14 июля 2011, 12:53
+
0
Вполне нормальный код. А что идеально в этом мире?
avatar

Sergei_T

  • 14 июля 2011, 23:20
+
0
Ну просто помоему не совсем оптимально использовать кучу различных утилит, когда можно обойтись простым скриптом на ruby или другом ЯП… Вообще вот еще одно интересное решение с использованием ruby bash и xmpp illuthion.com/?p=228
avatar

illuthion

  • 15 июля 2011, 00:05
+
0
не совсем оптимально использовать кучу различных утилит
это и есть unix-way — зачем тебе писать то, что уже написано?
avatar

Sergei_T

  • 15 июля 2011, 12:06
+
0
Ну, это дело спорное. Дело даже не в возможности попрактиковаться, а в том, что каждая строка bash-скрипта — это отдельный процесс со своими накладными расходами. Ему нужно выполнить массу вспомогательных операций: загрузиться в память (с жесткого диска), а после окончания — корректно очистить память. Да, консольные утилиты малы и легковесны, но в совокупности могут набегать существенные затраты и в ответственных системах такие решения не всегда адекватны. Другое дело — специально написанное приложение, где нет избыточности.
avatar

yababay

  • 15 июля 2011, 13:37
+
0
Зачотный блог , добавил в свою rss-подписку.
avatar

yababay

  • 15 июля 2011, 13:31

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.