IRC-Bot Klassendesign



  • 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.



  • Ich habe mir hier einmal das Interface des event_managers überlegt.

    #ifndef EVENT_MANAGER_HPP
    #define EVENT_MANAGER_HPP
    
    #include "socket.hpp"
    
    #include <string>
    #include <functional>
    
    #include <boost/noncopyable.hpp>
    
    namespace irc
    {
    	enum event_type
    	{
    		USER_JOIN,
    		USER_PART,
    		USER_KICK,
    		USER_BAN,
    		USER_QUIT,
    		...
    	};
    
    	// alle funktionen thread-safe
    	class event_manager : public boost::noncopyable
    	{
    	public:
    		typedef std::function<void(std::vector<std::string> args, event_manager& instance)> event_handler;
    
    		event_manager(irc::socket& socket);
    
    		void register_handler(event_type type, const std::string& chan, event_handler handler); // channel-spezifischer handler
    		void register_handler(event_type type, event_handler handler); // globaler handler
    
    		void unregister_handler(event_handler handler);
    
    		void write_line();
    		std::string read_line();
    
    		std::string nick() const;
    		std::string server() const;
    		unsigned short port() const;
    	};
    }
    
    #endif
    


  • void unregister_handler(event_handler handler);
    

    Da bin ich aber echt gespannt, wie die Implementierung aussieht 🙂

    Eventuell ist auch Boost.Signal was für dich...



  • Nexus schrieb:

    void unregister_handler(event_handler handler);
    

    Da bin ich aber echt gespannt, wie die Implementierung aussieht 🙂

    Eventuell ist auch Boost.Signal was für dich...

    Ja ich habe daran gedacht, das mit boost::signal zu implementieren, genauer gesagt mit boost::signal2 🙂

    Allerdings ist mir gerade ein anderes Konzept eingefallen, das mir auch recht gut gefällt. Und zwar zwei abstrakte Basisklassen "packet" und "event" von denen dann weitere packet- und event-Klassen für jeden Event-Typ abgeleitet werden. Dadurch kann man anstatt nur die Argumente am Handler zu bekommen direkt mit den geparsten Daten arbeiten und muss sich die Strings nicht selbst zusammenbauen.


Anmelden zum Antworten