Was wird erstellt?
Ein IRC-Client, der auch bestimmte Befehle reagieren kann. Dieser ruft eine Webseite auf und liefert das Ergebnis als Chatnachricht zurück.Erkenntnisse
Funktionsweise vom IRC. Einblicke in NodeJS als Server-Client-Gespann.
Vorarbeiten
Der Client verbindet sich über eine gesicherte Verbindung (SSL) mittels TLS.
Wir brauchen:
- NodeJS (installiert oder auch nur die ausführbare Datei)
http://nodejs.org/download/ - IRC Account auf einem Server
- Webseite, die einem was interessantes liefern
Konstrukt
Hier sind einige Elemente verbaut.Mehrer Hilfsfunktionen und viele anonyme Funktionen.
Das Konstrukt soll auch zeigen, was alles möglich mit Javascript ist.
Zuerst generieren wir uns ein Objekt mit den Server-Zugangsdaten.
Der ganze Server ist einer Funktion eingebettet.
Die Hilfsbereiche sind zum Teil direkt in der Daten-Funktion eingebunden und zum Teil in einem Hilfsobjekt ausgelagert. Das soll zeigen, dass mehrer Methoden möglich sind.
Ablauf
- Verbindung zum Server
- Login
- Registrieren von Event-Handler für den eingehenden Datenverkehr
- Auswerten der eingehenden Texte
- Aufrufen von entsprechenden Funktionen
- Antworten in den Chat senden
Code
var config = {
user: {
nick: 'sasobotli',
user: 'Saso_T_2013',
real: 'saso Bot - SN2013',
pass: '20cC12#'
},
server: {
addr: 'testirc.MEINSERVERNAME.de',
port: 6697
},
chans: ['#team-chat']
//chans: ['#chan1','#chan2']
, keywords:{}
}
// http://www.ietf.org/rfc/rfc1459.txt
irc3(); // startet den IRC-Bot
function irc3() {
// SSL Verbindung
var tls = require('tls');
var cleartextStream = tls.connect(
config.server.port,
config.server.addr,
null,
// anonyme Funktion, die das Anmelden auf dem Server macht
function(){
irc.stream = cleartextStream;
// ein paar Log-Ausgaben, um mehr zu verstehen
console.log("client connected mit "+
cleartextStream.remoteAddress+":"+
cleartextStream.remotePort);
console.log("meine adresse: ",
cleartextStream.address());
console.log('client connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized');
// nun senden wir unsere Zugangsdaten
irc.senden('PASS '+config.user.pass);
irc.senden('NICK '+config.user.nick);
irc.senden('USER '+config.user.user+
' localhost '+config.server.addr+
' '+config.user.real);
// in alle channels anmelden
for (var a=0;a<config.chans.length;a++) {
irc.senden('JOIN '+config.chans[a]);
}
// kommandozeile einbinden,
// so können wir auch direkt mit dem Server sprechen :)
process.stdin.setEncoding('utf8');
process.stdin.pipe(cleartextStream);
process.stdin.resume();
}
);
// das Encoding setzen
cleartextStream.setEncoding('utf8');
// nun registrieren wir eine Funktion,
// die auf eingehende Daten reagieren soll
cleartextStream.on('data', function(data){
console.log("incoming");
// daten zeilenweise abarbeiten
data = data.split('\n');
for (var i = 0; i < data.length; i++) {
// leere zeilen überspringen
if (data[i].trim() == "")
continue;
console.log('RECV -', data[i]);
if (data !== '') {
// anforderungen / request vom Server sich zu melden
var pos = data[i].indexOf(":");
var key = data[i].substr(0,pos).trim().toUpperCase();
var value = data[i].substr(pos+1).trim();
if (key != "") {
switch (key) {
case "PING":
irc.senden('PONG :'+value);
break;
default:
console.log("Nicht abgefangen: "+key);
}
} else {
// antwort oder info vom Server
var teile = value.split(" ");
//console.log(teile);
switch(teile[1]) {
case '353': // name reply, für NAMES,
// also alle Chatter in einem Raum erhalten
//console.log("Namen eingetroffen");
var namen = value.split(":")[1].trim().split(" ");
//console.log("gefunden: "+namen.length, namen);
for (var a=0;a<namen.length;a++){
var oname = namen[a].replace(/@/,"");
var admin = false;
if (namen[a].match(/@/))
admin = true;
// irc ist unten als Objekt definiert
irc.users[oname] = {'name':oname, 'admin':admin, 'nachrichten':[]};
}
//console.log(irc.users);
break;
case 'JOIN': // Raum beigetreten
var raum = teile[2].substr(1);
// ein paar Begrüssungen
var sp = ['Hallo', 'Hey', 'hi', 'Wie gehts?',
'huhu','Toll, dass du da bist'];
// eine Begrüssung per Zufall auswählen
var pos = Math.round(Math.random()*(sp.length-1));
// begrüssen
irc.senden("PRIVMSG "+raum+" "+sp[pos]);
// alle Namen im Raum abrufen
irc.senden("NAMES "+raum);
break;
case 'PRIVMSG':
// nachricht eingetroffen
// wir speicher wann zuletzt was gesagt wurde
irc.zaehler.lastword = time();
// ermittel ob direkt oder raum chat
var sender = teile[0];
var empfaenger = teile[2];
var user_sender = sender.split("!")[0];
var antwort = teile.slice(3)
.join(" ").trim()
.substr(1); // ohne führendes :
// wir speichern den User in unsere Liste
if (!irc.users[user_sender]) {
irc.users[user_sender] = {
'name':user_sender,
'admin':false,
'nachrichten':[], keywords:{}};
}
// speichern die Nachricht in seine History
irc.users[user_sender].nachrichten.push(antwort);
//console.log("nachricht eingegangen. Von "+sender, antwort);
// wurden wir direkt angesproche? also privatchat?
if (empfaenger.toLowerCase() ==
config.user.nick.toLowerCase()) {
var meineantwort = "danke, selber";
// senden unsere Antwort
irc.senden('PRIVMSG '+user_sender+" "+meineantwort);
} else {
// channel message eingetroffen
//irc.senden('PRIVMSG '+empfaenger+" Roger that");
console.log("wuerden senden: "+'PRIVMSG '+empfaenger+" Roger that");
}
// hier können wir nun das Gesagt analysieren und
// dementsprechend antworten
// hat jemand geschrieben: sag was,
// starten wir den Sprüchemodus
if (antwort.toLowerCase() == "sag was"){
console.log("sprueche intervall modus aktiv");
// einen Spruch holen und senden
irc.sagwas();
}
if (antwort.toLowerCase() == "sag sofort was"){
irc.sagwas(true);
}
if (antwort.toLowerCase() == "stopp"){
console.log("sprueche modus deaktiviert");
irc.zaehler.aktiv = false;
}
if (antwort.toLowerCase() == "saso start"){
console.log("sprueche modus aktiviert");
irc.zaehler.aktiv = true;
}
// wenn jemand fragt, woher die Sprüche kommen
// geben wir die URL als Antwort
// diese Bereich kann man gut ausbauen und
// dem Bot Befehle zum Auswerten von anderen Diensten
// geben. So dass er die Ergebnisse hier postet
if (antwort.toLowerCase().match(/woher\s+sind\s+die/)){
irc.senden("PRIVMSG "+empfaenger+" http://sprueche.woxikon.de");
}
// wenn jemand die History haben will ...
if (antwort.toLowerCase().match(/history:/)) {
// könnten wir hier diese auswerten und posten...
}
break;
}
}
}
}
});
cleartextStream.on('end', function(){
console.log("session beendet");
process.exit(0);
});
// Objekt, dass uns als zwischenspeicher dient
// und auch hilfsfunktionen enthält
var irc = {
zaehler: {
'aktiv':false,
'lastword':time()
},
stream: null,
// sendet die Nachrichten an den IRC-Server
senden: function(text) {
console.log("SEND -", text);
this.stream.write(text+'\n');
if (text.match(/^JOIN/i)) {
// hole die benutzer
this.senden("NAMES"+text.substr(4));
}
},
// hilfsfunktion, die später für das Chatten genutzt wird
sagwas: function(force){
if (!force) {
// zufall, wann wieder ein neuer Spruch gesagt wird
var nextcall = Math.round(Math.random()*(10))+3; // minuten
setTimeout(irc.sagwas, nextcall*1000*60);
console.log("nächster spruch ca. in "+nextcall+" minuten");
}
// nur wenn vor 2 minuten niemand was gesagt hat
if (!force && irc.zaehler.lastword > time()-(2*1000*60))
return;
if (!force && !irc.zaehler.aktiv)
return;
irc.zaehler.lastword = time();
// ein paar sprüche seiten
var urls = [
{'url':'http://sprueche.woxikon.de/poesiealbum','satz':'Das hier find ich gut:'},
{'url':'http://sprueche.woxikon.de/emo', 'satz':'kennt ihr den?'}];
var pos = Math.round(Math.random()*(urls.length-1));
var spruchurl = urls[pos];
// hier holen wir die Sprüche ab und
// senden diese dann direkt als Message
URLLaden(spruchurl.url, function(html) {
// suchen im HTML nach den Sprüchen
var treffer = html.match(/<div\s+class="jingle-content">([\S\s]*?)<\/div>/gi);
var sprueche = [];
for (var a=0;a<treffer.length;a++) {
//console.log(treffer[a]);
sprueche.push(treffer[a].replace(/(<\/?[^>]+>)/gi, ''));
}
// wenn es einige Treffer gibt, wählen wir per zufall einen aus
if (sprueche.length>0) {
//console.log(sprueche);
var pos = Math.round(Math.random()*(sprueche.length-1));
var spruch = sprueche[pos].trim().replace(/\s/," ");
//console.log("kennt ihr den? "+spruch);
irc.senden('PRIVMSG '+config.chans[0]+" "+spruchurl.satz+" "+spruch);
}
});
},
users: {}
}
}
function time(onlyseconds) {
var datum = new Date();
var milliseconds = datum.getTime();
if (onlyseconds)
return intval(milliseconds/1000);
return milliseconds;
}
// diese Funktion, lädt eine HTML Seite
function URLLaden(url, cbfkt) {
if (url.match("://")) {
var http;
if (url.match("s://")){
http = require('https'); // die URL verlangt SSL
} else {
http = require('http');
}
http.get(url, function(res) {
//console.log("Got response: " + res.statusCode);
var html = "";
res.on("data", function(chunk){
html += chunk; // inhaltteile der Seite zusammen kleben
});
res.on("end", function(){
// alle Teile sind angekommen und wir haben die HTML-Seite
// cbfkt ist eine anonyme Fkt, die wir aufrufen
// als ergebnis, senden wir das HTML
cbfkt(html);
});
res.on("close", function(err) {
// verbindung beendet
cbfkt(html);
console.log("Error holen "+url);
});
}).on('error', function(e) {
// verbindung getrennt
console.log("Got error: " + e.message);
});
} else {
// falls die URL auf eine lokale Datei zeigt, laden wir diese halt
fs.readFile(url, function(err, data){
if (err){
console.log(err);
} else {
cbfkt(data);
}
});
}
}
Weiterführende Lektüre:
- Node.js & Co: Skalierbare, hochperformante und echtzeitfähige Webanwendungen professionell in JavaScript entwickeln
- IRC Hacks: 100 Industrial-Strength Tips & Tools
Fazit
So kann man sehr schnell einen eigenen Überwachungsserver schreiben. Dieser meldet einfach den Status im IRC-Chat. Natürlich kann man das beliebig aufbohren. Man sieht aber wieder, wie schnell und unkompliziert man mit NodeJS Lösungen erstellen kann.
Viel Spass
Saso
IRC Server aufsetzen incl. SSL-Verschlüsselung aufsetzen: http://sebastianhemel.blogspot.de/2013/05/eigenen-irc-server-aufsetzen.html
AntwortenLöschen