IRC-Bot Klassendesign



  • Huhu,

    Ich würde gerne einen IRC-Bot in C++ programmieren. (Das Projekt Canasta KI steht jetzt einmal 😃 ) Ich hab schon mehrere IRC-Bots programmiert, auch schon einen in C++, allerdins möchte ich diesmal endlich mal ein schönes Design haben. Der Bot soll leicht erweiterbar sein, auf Events soll mittels Eventhandlern reagiert werden können, und das ganze soll parallelisierbar sein. Viele Anforderungen, die ich mir hier selbst stelle, aber mittlerweile kann ich mit boost::asio und boost::thread einigermaßen umgehen, daher sollte ich das packen. 🙂

    Ich hätte nun gerne ein paar allgemeine Ratschläge, was für Klassen ihr programmieren würdet, wie das Eventhandling aussehen könnte und überhaupt wie alles funktionieren soll, denn wie gesagt geht es mir hier um ein schönes Design.

    Grüße,
    PI



  • Soll er nur auf einem channel laufen oder auf mehreren?

    Sind nur Du und andere Programmierer Admin oder auch Laien?

    Schützt er den channel oder ist es ein Spaßbot, der auch mal offline gehen darf?

    Soll er loggen, wenn Du nicht online bist?

    Läuft er auf einem anderen Rechner, z.B. deinem gemieteten root-server?

    Soll er Wissen über die User sammeln?



  • Der Bot soll in mehreren Channels sein können (Ich dachte da an einen Thread je Channel, allerdings habe ich mir noch nicht überlegt, wie genau der Thread informiert werden soll.)

    Der IRC-Bot ist ein 2-Mann-Projekt, ich mit mir selbst 😃
    Allerdings könnte eine Rechte-Verwaltung recht praktisch sein.

    Er soll sowohl Schutzfunktionen haben, als auch Spaßfunktionen. Offline gehen sollte er eigentlich nicht, aber einen quit-Befehl sollte er haben. 😉

    Logging ist auf alle Fälle praktisch, aber nicht das wichtigste.
    Wissen über User sammeln brauche ich (vorerst) auch nicht. Allerdings sollte er so erweiterbar sein, dass das nachträglich eingebaut werden kann.

    Der Bot wird auf meinem Ubuntu 11.04 VServer laufen.

    Edit: Ich dachte auch an die Möglichkeit, mittels boost::python Pythonscripte verwenden zu können.



  • Eigentlich braucht der Bot ja dann nur 5 Dinge können:
    1. Antwort auf eingehende PINGs mit einem PONG
    2. Joinen/Verlassen von Channels
    3. Parsen von Nachrichten
    4. Aufruf eines Scrips bei bestimmten Nachrichteninhalt oder Aufbau.
    5. Antworten in einen Channel schreiben

    Wenn man diese Grundkonzepte aufgebaut hat, dann geht alles weitere auch ohne Probleme.

    Die Vergabe von Rechten wäre damit auch recht einfach lösbar. Wenn man das Joinen auch beobachtet, dann kann man auch unmittelbar nach dem Joinen Rechte vergeben, sofern der Benutzer dafür autorisiert ist.



  • klöklö schrieb:

    Eigentlich braucht der Bot ja dann nur 5 Dinge können:
    1. Antwort auf eingehende PINGs mit einem PONG
    2. Joinen/Verlassen von Channels
    3. Parsen von Nachrichten
    4. Aufruf eines Scrips bei bestimmten Nachrichteninhalt oder Aufbau.
    5. Antworten in einen Channel schreiben

    Wenn man diese Grundkonzepte aufgebaut hat, dann geht alles weitere auch ohne Probleme.

    Die Vergabe von Rechten wäre damit auch recht einfach lösbar. Wenn man das Joinen auch beobachtet, dann kann man auch unmittelbar nach dem Joinen Rechte vergeben, sofern der Benutzer dafür autorisiert ist.

    Ich weiß schon, was der Bot können muss, ist ja schließlich nicht mein erster.(müsste etwa der 8te sein) Allerdings ist es mir wirkich wichtig, diesmal ein schönes Design zu haben, die letzten waren alle unter aller Sau 😉



  • 314159265358979 schrieb:

    Ich weiß schon, was der Bot können muss, ist ja schließlich nicht mein erster.(müsste etwa der 8te sein) Allerdings ist es mir wirkich wichtig, diesmal ein schönes Design zu haben, die letzten waren alle unter aller Sau 😉

    Na, dann überleg dir warum die so schlecht waren und probier es beim 9ten mal besser zu machen. Wenn das immer noch nicht gut ist, dann machst dus halt ein 10tes mal. Bis es dir passt oder die Lust vergangen ist. 😉



  • 314159265358979 schrieb:

    Er soll sowohl Schutzfunktionen haben, als auch Spaßfunktionen. Offline gehen sollte er eigentlich nicht, aber einen quit-Befehl sollte er haben. 😉

    Dann wüde ich ihn schonmal in zwei Schichten aufteilen.
    u) Connecten und bei Diskonnect wiederconnecten und den ganzen Schutz. Der kann in die exe reincompiliert werden.
    o) Alles Spaß. Der wird per dll oder so nachgeladen und ist zur Laufzeit austauschbar, während die Sicherheit duchläuft, insbesondere bleibt die Connection bestehen.



  • Ich hab jetzt einfach mal irgendwie angefangen, connecten funktioniert auf den ersten Anlauf 🙂
    Bitte sagt mir, was gut, und was schlecht ist.

    #ifndef SOCKET_HPP
    #define SOCKET_HPP
    
    #include <string>
    #include <istream>
    #include <vector>
    #include <iostream>
    
    #include <boost/asio.hpp>
    #include <boost/lexical_cast.hpp>
    #include <boost/noncopyable.hpp>
    #include <boost/algorithm/string.hpp>
    
    namespace irc
    {
    	namespace io
    	{
    		using namespace boost::asio;
    		typedef ip::tcp::socket socket;
    		typedef ip::tcp::resolver resolver;
    	}
    
    	class socket : public boost::noncopyable
    	{
    		const std::string server;
    		std::string nick;
    		const unsigned short port;
    
    		io::streambuf buffer;
    		io::socket sock;
    
    		std::vector<std::string> split(const std::string& str, const std::string& delim)
    		{
    			std::vector<std::string> strs;
    			boost::split(strs, str, boost::is_any_of(delim));
    			return std::move(strs);
    		}
    
    		std::string read_line()
    		{
    			io::read_until(sock, buffer, '\n');
    
    			std::istream is(&buffer);
    			std::string line;
    
    			std::getline(is, line);
    
    			if(line[line.length() - 1] == '\r')
    				line.resize(line.length() - 1);
    
    			if(line[1] == 'I')
    			{
    				line[1] = 'O';
    				write_line(line);
    				return read_line();
    			}
    
    			return line;
    		}
    
    		void write_line(const std::string& line)
    		{
    			io::write(sock, io::buffer(line + "\r\n"), io::transfer_all());
    		}
    
    	public:
    		socket(io::io_service& io_service, const std::string& server, const std::string& nick, unsigned short port = 6667)
    			: server(server)
    			, nick(nick)
    			, port(port)
    
    			, buffer(512)
    			, sock(io_service)
    		{
    			io::resolver resolver(io_service);
    			io::resolver::query query(server, boost::lexical_cast<std::string>(port));
    
    			io::resolver::iterator endpoint_iterator = resolver.resolve(query);
    			io::resolver::iterator end;
    
    			boost::system::error_code error = boost::asio::error::host_not_found;
    			while(error && endpoint_iterator != end)
    			{
    				sock.close();
    				sock.connect(*endpoint_iterator++, error);
    			}
    
    			if(error)
    				throw boost::system::system_error(error);
    
    			write_line("NICK " + nick);
    			write_line("USER " + nick + " 0 0 :" + nick);
    
    			do
    			{
    				std::vector<std::string> args = split(read_line(), " ");
    				if(args.size() >= 1 && args[1] == "376")
    					break;
    			}
    
    			while(true);
    
    			for(;;)
    			{
    				std::cout << read_line() << std::endl;
    			}
    		}
    	};
    }
    
    #endif // SOCKET_HPP
    
    #include "socket.hpp"
    
    int main()
    {
    	irc::io::io_service io_service;
    	irc::socket socket(io_service, "irc.quakenet.org", "ThePiBot");
    }
    

    (Die Ausgabe im Header ist nur temporär und kommt natürlich noch weg.)



  • Was hast das jetzt mit Design zu tun?

    Zeig eher mal wie du die Klassenhierarchie anlegst und wie die miteinander arbeiten sollen. In dieser Stufe bereits Code auszuprogrammieren zu wollen ist nicht der richtige Weg und hat nichts mit Design zu tun.

    Ich würde mal wetten, dass du die anderen Bots auch so angefangen hast. 😉
    Überleg dir besser im vornherein wie alles miteinander arbeiten soll und wo es erweiterbar sein soll und wie das löst.



  • Bitte einfach nur die Deklartionen, wo nötig Kommentare. Bitte kein UML. Noch viel viel besser wäre Prosa, aber das schaffen wenige so hinzukriegen, daß es einerseits nicht zusammenhanglos gestammelt ist, und andererseits nicht leer ist. Eine nette Mischung mach mal, beschreibe es so, daß ich es verstehe, obwohl ich es nicht vorher schon kannte.



  • Nun, ich habe mir gedacht, für die Events einen event_manager oder etwas vergleichbares zu basteln, der die Eventhandler intern abspeichert und am Socket ständig liest. Der soll dann die Nachrichten parsen und die entsprechenden Handler aufrufen.



  • Dann kannst du das auch mal tun, indem du einfach mal die Klassen dazu erstellst. Die nötigen öffentlichen Funktionen dazu und dann in etwa wie die zusammen arbeiten sollen. Dann siehst du relativ schnell ob das gut zusammenspielt und wo du Sachen verallgemeinern kannst, damit es erweiterbarer wird.

    Ich weiss nicht ob es so viel bringt das hier breit auszudiskutieren. Du kannst mal die grobe Struktur machen, denken, implementieren. Am Ende weisst du dann ja ob es gut war oder nicht. Du kannst natürlich im vornherein Ratschläge hier einholen, aber imo lernt man so Sachen besser, wenn man das einfach mal selber macht und daraus lernt. Und beim nächsten mal kannst du es besser machen.



  • @volkard: Prosa?
    Hier mal die Deklaration:

    #ifndef SOCKET_HPP
    #define SOCKET_HPP
    
    #include <string>
    #include <vector>
    
    #include <boost/asio.hpp>
    #include <boost/noncopyable.hpp>
    
    namespace irc
    {
    	// Um code übersichtlicher zu machen
    	namespace io
    	{
    		using namespace boost::asio;
    		typedef ip::tcp::socket socket;
    		typedef ip::tcp::resolver resolver;
    	}
    
    	// Stellt eine _eindeutige_ Verbindung zum IRC dar
    	class socket : public boost::noncopyable
    	{
    		const std::string server;
    		std::string nick; // Wichtig für Identifizierung, von einem Host, können mehrere Clients kommen
    		const unsigned short port;
    
    		io::streambuf buffer; // Lesepuffer
    		io::socket sock; // "roher" TCP Socket
    
    		std::vector<std::string> split(const std::string& str, const std::string& delim); // Hilfsfunktion um einen String anhand von Leerzeichen zu splitten
    
    		// \r\n wird abgeschnitten / angehängt
    		std::string read_line();
    		void write_line(const std::string& line);
    
    	public:
    		socket(io::io_service& io_service, const std::string& server, const std::string& nick, unsigned short port = 6667);
    	};
    }
    
    #endif // SOCKET_HPP
    


  • 314159265358979 schrieb:

    @volkard: Prosa?
    Hier mal die Deklaration:

    #ifndef SOCKET_HPP
    #define SOCKET_HPP
    
    #include <string>
    #include <vector>
    
    #include <boost/asio.hpp>
    #include <boost/noncopyable.hpp>
    
    namespace irc
    {
    	namespace io
    	{
    		using namespace boost::asio;
    		typedef ip::tcp::socket socket;
    		typedef ip::tcp::resolver resolver;
    	}
    
    	class socket : public boost::noncopyable
    	{
    		const std::string server;
    		std::string nick;
    		const unsigned short port;
    
    		io::streambuf buffer;
    		io::socket sock;
    
    		std::vector<std::string> split(const std::string& str, const std::string& delim);
    
    		std::string read_line();
    		void write_line(const std::string& line);
    
    	public:
    		socket(io::io_service& io_service, const std::string& server, const std::string& nick, unsigned short port = 6667);
    	};
    }
    
    #endif // SOCKET_HPP
    

    Jupp, jetzt reden wir eine Sprache.
    Prosa: Schreib normale deutsche Sätze dazu, was, warum, weshalb und warum nicht.

    Du schreibst, ein socket HAT einen nick. Mööp.



  • Editiert, war das so gemeint?



  • Das ist alles noch ziemlich low-level. Setz höher an. Was eine Klasse für Daten-Member und Hilfsfunktionen hat interessiert erstmal nicht.

    Interessant ist erstmal was du überhaupt für Klassen hast, und für was die zuständig sind. Und wer wie mit wem kommuniziert.



  • hustbaer schrieb:

    Das ist alles noch ziemlich low-level. Setz höher an. Was eine Klasse für Daten-Member und Hilfsfunktionen hat interessiert erstmal nicht.

    Naja, die meisten Daten-Member sind schon höchst interessant. Viel interessanter, als diese oder jene öffentliche Methode, die eh problemlos ist.
    Wie hier, wo ich den nick im socket entdeckt habe.
    (Deswegen mag ich auch im Code die Members vorne.)



  • Wo würdest du den Nick denn sonst hingeben?



  • volkard schrieb:

    hustbaer schrieb:

    Das ist alles noch ziemlich low-level. Setz höher an. Was eine Klasse für Daten-Member und Hilfsfunktionen hat interessiert erstmal nicht.

    Naja, die meisten Daten-Member sind schon höchst interessant. Viel interessanter, als diese oder jene öffentliche Methode, die eh problemlos ist.
    Wie hier, wo ich den nick im socket entdeckt habe.
    (Deswegen mag ich auch im Code die Members vorne.)

    Also ich sehe das anders.
    Was den Nick angeht, das siehst du im Prinzip genau so im Ctor.
    Und ich finde gar nicht dass der dort nicht hingehört. Nur heisst die Klasse falsch, die sollte "irc_session" o.ä. heissen.



  • 314159265358979 schrieb:

    Wo würdest du den Nick denn sonst hingeben?

    Vielleicht in die Sitzung.


Log in to reply