Journal: Neueste Artikel erscheinen hier (Seite 5 von 21)
XMPP (Jabber) mit Ruby am Beispiel FelicianXMPPBot
Warning: filesize() [function.filesize]: stat failed for /home/jail/bxt/bxt/de.bernhardhaeussner/htdocs/upl/FelicianXMPPBot_0.9.tar.gz in /home/jail/bxt/bxt/de.bernhardhaeussner/lib/php/engine.classes.php on line 229

FelicianXMPPBot in action
Das Protokoll XMPP für den (Nahezu-)Echtzeitaustausch von XML-Nachrichten (meist verwendet für IM, als „Jabber“) steht in Konkurrenz und Verbindung mit einigen aktuell mehr oder weniger neuen Technologien, die mit XML und realtime zu tun haben, wie RSS/Atom pings, realtime web oder AJAX. Und ein paar Spielereien auf diesem Gebiet können sicher nicht schaden, also habe ich mit XMPP etwas herumgebastelt.
Weil dies mit PHP irgendwie seltsam wäre und David Strohmayers Text und Präsentation über XMPP mit Java noch nicht existierte, habe ich das ganze mit Ruby gebastelt, da gibt es nämlich den Gem xmpp4r, der mit vielen Beispielen kommt. So konnte ich mal ein bisschem mit Ruby herumspielen. (Zwei Fliegen mit einer Klappe usw.)
Aus den Experimenten ist ein kleiner Server geworden, der einen Jabber-Bot betreibt und über ein einfaches TCP-Protokoll erreicht werden kann, sodass aus allen TCP-Fähigen Skripten (bash mit netcat, PHP…) einfach Nachrichten an einen Jabber-Endpunkt versendet werden können.
Code und Funktionalität bestehen aus drei Teilen:
Die Klasse Netx kümmert sich um den TCP-Server und parst das einfache Protokoll: Man schickt Zeilen und beendet einzelne Nachrichten mit einem Punkt, und die Verbindung (und damit auch die aktuelle Nachricht) mit zwei Punkten. So lassen sich die TCP-Clients leicht implementieren.
Die Klasse XmppRoute bietet nur ein wenig Grundfunktion für den Jabber-Bot. Sie vereinfacht Verbinden und Senden noch weiter, zudem extrahiert sie beim Empfang einer Nachricht einen Befehlsteil.
Der Rest kümmert sich um die Verbindung der beiden Komponenten und um das Parsen von Startup-Parametern.
Aber genug der vielen Worte, jetzt folgt der Code:
jabot.rb
#!/usr/bin/ruby
# Felician 0.9 - XMPP Wrapper
# (c) 2009 by Bernhard Häussner
# Licence: GPLv2+ http://www.gnu.org/licenses/gpl-2.0.txt
#
# This jabber wrapper will recieve messages via simple tcp.
#
# Quit by sending "exit" via jabber.
# Send "info" via jabber to get running parameters.
# Send "feedback [msg]" via jabber to post from jabber.
# Connect to specified port and send messages terminated by lone "." in a line to post via TCP.
# Send a line with ".." to quit connection.
#
# Based on xmpp4r-0.5/data/doc/xmpp4r/examples/basic
#
require 'optparse'
require 'XmppRoute'
require 'Netx'
class FelicianXMPPBot
def start(his_JID,my_txt_JID,my_password,listen_port)
puts "FELC: Started. Sending to #{his_JID} from #{my_txt_JID} listening to #{listen_port}."
my_JID=JID.new(my_txt_JID)
netx=Netx.new
XmppRoute.connect(my_JID,my_password) do
on_recieve do |from,cmd,param|
if from.strip.to_s.eql?his_JID then
case cmd.downcase
when 'exit' then
send(from,"-> I've got to go!")
puts "FELC: Got shutdown signal, waiting for children to terminate..."
netx.halt
when 'feedback' then send(from,param)
when 'info'then send(from,"-> Felician sending to #{his_JID} listening to #{listen_port}. ")
else send(from,"-> I don't know: #{cmd} #{param}")
end
else
puts "XMPP: Ignored a message. "
send(from,"Hi, #{from.strip.to_s}. Sorry, could not be deliverd. Try again later! ")
end
end
netx.on_sended do |msg|
send(his_JID,msg.strip)
puts "XMPP: Client message sent. "
end
netx.listen(listen_port)
end
puts "FELC: Shutting down. "
end
end
if $0 == __FILE__
#defaults:
listen_port=2000
his_JID = "bot_admin@gmx.de"
my_txt_JID = "i_am_a_bot@jabber.ccc.de/felician"
my_password = "top_secret"
# cli settings
OptionParser.new do |opts|
opts.banner = 'Usage: ruby jabot.rb -j jid/ressource -p password -t reciever_jid -l port'
opts.separator ''
opts.on('-j', '--jid JID', 'sets the sender jid') { |j| my_txt_JID = j }
opts.on('-p', '--password PASSWORD', 'sets the sender password') { |p| my_password = p }
opts.on('-t', '--to-jid JID', 'sets the message reciever') { |t| his_JID = t }
opts.on('-l', '--listen PORT', 'sets the server listen port') { |l| listen_port = l }
opts.on_tail('-h', '--help', 'Show this message') {
puts opts
exit
}
opts.parse!(ARGV)
end
FelicianXMPPBot.new.start(his_JID,my_txt_JID,my_password,listen_port)
end
XmppRoute.rb
require 'rubygems'
require 'xmpp4r/client'
include Jabber
class XmppRoute
def initialize(jid,pwd)
@cmd=Proc.new { |a,b,c| puts "XMPP: No reciever. " }
@cl = Client.new(jid)
@cl.connect
@cl.auth(pwd)
@cl.send(Presence.new)
puts "XMPP: Connected! send messages to #{jid.strip.to_s}."
@cl.add_message_callback { |m| process m }
end
def close
@cl.close
end
def on_recieve(&cb)
@cmd=cb
end
def send(to,mes)
ms = Message.new(to,mes) # [_CY4_L8-R_4LLY-G4T3-R_]
ms.type = :chat
@cl.send(ms)
end
private
def process(m)
puts "XMPP: Got msg of type #{m.type} / #{m.name} with -#{m.body}- "
if m.type == :chat && ! m.body.nil?
index=(m.body.index(" ") || 0)-1
com=m.body[0..index]
param=(index==-1?"":m.body[index+2..-1])
puts "XMPP: Call #{com} with --#{param}-- (caused from #{m.from})"
@cmd.call(m.from,com,param)
end
end
def self.connect(jid,pwd,&block)
bot=XmppRoute.new(jid,pwd)
bot.instance_eval(&block)
bot.close
end
end
Netx.rb
require 'socket' # Get sockets from stdlib
class Netx
def initialize; @run=true; end
def listen(port)
puts "NETX: Bind to Port #{port}"
#server = TCPServer.open(port)
#only local:
server = TCPServer.open("127.0.0.1",port) # Socket to listen
while @run
# this timeout allows us to kill this lop every few seconds...
begin
session = Timeout::timeout(10) { server.accept }
Thread.start(session) { |c| connection c }
rescue TimeoutError
#puts "NETX: Refreshing TCPServer."
end
end
end
def on_sended(&cb); @client_cb=cb; end
def halt; @run=false; end
private
def connection (client)
puts "NETX: Client conntected..."
client.puts("-- Hello on Felician at #{Time.now.ctime}. ") # Send the helo & time to the client
conn_open=true;
msg_buffer="";
while conn_open do
l = client.gets
if (l.strip.eql?'..') || (l.strip.eql?'.') then
puts "NETX: Client issued a message..."
client.puts("-- accepted. ")
@client_cb.call(msg_buffer)
msg_buffer="";
conn_open=false if l.strip.eql?'..'
else
msg_buffer << l
end
end
ensure
client.puts "-- Closing the connection. Bye!"
puts "NETX: Client disconnect. "
client.close # Disconnect from the client
end
end
Erwartet nicht zu viel von dem Code, ist schließlich mein erster Ruby-Code, abgesehen von einigen Hello-World-Sachen. Funktioniert dafür allerdings erstaunlich gut und könnte sich schon fast eigenen, um z.B. einen Systemadministrator über wichtige Aktivitäten und Warnungen aus den verschiedensten Programmen und Wartungsscripten schnell zu informieren.
Man beachte vielleicht noch das alles in dem Code, was in anderen Programmiersprachen als schlechter Stil gälte, hier eher „the ruby way“ ist ;).
Benutzung
Eine Nachricht von der Shell schicken:
09:15 burny@home:~> echo -e "Hallo Welt\n.." | netcat localhost 2000 -- Hello on Felician at Tue Dec 15 21:16:02 2009. -- accepted. -- Closing the connection. Bye!
Mal sehen, ob ich das Ganze noch weiter entwickle, meistens ist doch das Senden einer ganz normalen eMail einfacher.
École

Router
Wenn ich mal nicht an meiner Facharbeit arbeite, arbeite ich meistens nicht zu produktiv. Aber immerhin...
Mich fasziniert immernoch die Symmetrie, die man mit dem Computer in Zeichnungen und Fotos schaffen kann. Mit GIMP lässt sich das machen:
Diese Bilder sind übrigens mehr oder weniger Weiterentwicklungen derer von den Italien-Reisen. Andere Inspirationsquellen: 1984, Ella und schätzungsweise Geometrie.
[Updated 2009-12-13] Genauso interessant scheint es die von Pixel suggerierte Symmetrie und Rechteckigkeit wieder zu brechen:
[Updated 2010-03-11] Auch noch etwas im Kontext der Italienreisen zu sehen ist dieses:
Rechnen mit KDE4 usw.

Klassische Taschenrechner
Was macht man mit einem Computer? Rechnen? Meistens eMails lesen und schreiben, im Internet surfen, Artikel, Blogeinträge, Briefe und Aufsätze verfassen, Daten einpflegen, zocken... und hin und wieder sogar Programmieren.
Aber hier soll es wirklich einmal um das Rechnen gehen, also das womit man einen Großteil der Grundschulzeit verbracht hat, und die Anwendungen, die man dazu unter Linux/KDE4 verwenden kann.
bc
Natürlich kann man den Linux-Konsolen-Rechner bc verwenden:
Er kann mit beliebiger Genauigkeit rechen, unterstützt Variablen und zeigt den Rechenverlauf an. Daher kann man ihn auch aus Shell-Scripten verwenden, was ihn ziemlich universal einsetzbar und automatisierbar macht.
[Update 2009-12-13] Zum Beispiel lässt sich so das kartesische Produkt der Zeilen zweier Dateien A und B durch-multiplizieren:
while read lA;do while read lB;do echo $lA*$lB;done <B;done <A|bc >result
ALT+F2=
Der Programmstarter kann auch Rechnen! Sobald im Einfabefenster ein = steht wird der Rest als Term interpretiert und das Ergebnis angezeigt. Das ist sehr praktisch, wenn man schnell ein paar Kosten aufaddieren will oder ähnliches. Man kann auch gut Experimentieren, da das Ergebnis schon beim Tippen angezeigt wird.
Für kompliziertere Rechnungen wird das aber schnell unübersichtlich. Außerdem hat der Rechner leider einen Rundungsfehler, statt z.B. 5,5 zeigt er öfters 5,49999 an usw.
KCalc
Der Klassische Rechner auf KDE ist KCalc:
Es hat alle Funktionen, die man von einem solchen Rechner erwarten könnte: Funktionstasten für diverse wissenschaftliche und mathematische Funktionen, Umrechnung verschiedener Zahlensysteme, und ein geordnetes Verzeichnis von Konstanten aus fast allen Wissenschaftsbereichen. Man kann natürlich auch auch einige Tasten ausblenden.
KAlgebra
Etwas erweiterte Funktionen hat KAlgebra:
Neben der bc-ähnlichen Konsole mit Vorschlägen beim Tippen, sowie einem 2D und 3D Plotter (Siehe Fun with KAlgebra), beinhaltet er außerdem eine Funktionsliste mit graphischer Vorschau.
Wolfram Alpha
Jetzt verlassen wir das KDE-Gebiet in Richtung Internet. Auf der Webseite Wolfram Alpha lassen sich nahezu alle denkbaren Berechnungen durchführen, auch Integrieren und Rechen mit exotischen Daten (Eigenschaften Chemischer Elemente, Aktienkurse, Mathematische Modelle, Wetter usw.). Zu fast jeder Berechnung gibt es einen Graphen oder eine Illustration, zum Integrieren die Rechenschritte, zu Funktionen diverse Daten und einiges mehr. Siehe die sehr lange Liste von Beispielen und meine Lifestream-Einträge zum Schlagwort Wolfram.
Nebenbei bemerkt...

Natural Display (links)
Ich für meinen Teil rechne oft mit dem klassischen Taschenrechner, in meinem Fall der fx-85EF oder der fx-85MS, von denen ich einen fast immer griffbereit habe. Obwohl sie nicht die kompliziertesten Modelle sind haben sie neben dem grundlegenden Funktionsumfang auch noch schöne Stochastik-Funktionen und Rechnen mit Prozenten und Grad, der EF hat ein Natural Display (also „schöne“ Anzeige von Brücken, Wurzeln usw. ) und Wertetabellen.
Außerdem habe ich einen Rechenschieber, mit dem sich schnell und Stromlos zumindest die Berechnungen -, +, *, /, sin, ^2, ^3, cos, arc, tan, lg berechnen lassen.
Für Windows hier eine Empfehlung für SpeedCrunch.
Einer der besten Rechner ist sicherlich der Graphing Calculator von Pacific Tech. Er zeigt nicht nur alle Rechnungen so an, wie im Mathebuch, sondern rechnet mit Vektoren und Komplexen zahlen und plottet sogar Vierdimensionales. Gibts allerdings nur für Mac und Windows und kostet seinen Preis.
KDE4 Twitter Benachrichtigungen
Da ich gerne das Webinterface von twitter benutze, aber auch gleich wissen will, ob jemand etwas neues getwittert hat, habe ich ein kleines Bash-Script geschrieben, welches eine Meldung aufploppen lässt, sollte jemand einen neuen tweet schreiben.
Es benutzt kdialog, die Meldungen integrieren sich also Problemlos in den KDE-Desktop. Es ist auch recht kurz und einfach.
Einfach herunterladen und in $PATH ablegen. Zum Starten genügt:
nohup twitterNotify.sh &
Da es nicht sonderlich viel Aufwand betreibt, um die XML-Antworten der Twitter-API zu parsen kann nicht zu viel Information übermittelt werden, außer woher die tweets kommen.
Javascript Regular Expressions Beispiele
Viele Javascript-Tutorials zum RegExp-Objekt benutzen die RegExp.$1-Notation und andere unschöne Dinge. Außerdem gibt es mehrere nahezu äquivalente Funktionen, was Reguläre Ausdrücke angeht. Darum habe ich ein Bisschen herumgespielt mit den Funktionen S.match(R), R.exec(S), R.test(S), S.split(R), S.replace(R) und S.search(R). Hier ist die kleine Referenz mit viel Beispiel, mit der ich hoffe arbeiten zu können:
RegExp-Objekte erstellen
// short constructor
var regexp=/a(b?c)/i
// long constructor
var regexp=new RegExp("a(b?c)","i"):
// -> allows runtime configuration
var char="a";
var regexp=new RegExp(char+"(b?c)",'i');
In der Praxis wird man fast immer den kurzen Konstruktor verwenden. Manchmal lässt sich ein dynamisches Erstellen des RegExps vermeiden, indem man den Treffer überprüft.
Finden mit String.match(RegExp) und RegExp.exec(String)
// match: basic usage
"a".match(regexp); // null
"abc".match(regexp); // ["abc", "bc"]
// -> "i"-flag: caseinsensitive
"Ac".match(regexp)); // ["Ac", "c"]
"acb".match(regexp); // ["ac", "c"]
// exec: same but method of regexp
regexp.exec("a"); // null
regexp.exec("abc"); // ["abc", "bc"]
regexp.exec("Ac"); // ["Ac", "c"]
regexp.exec("acb"); // ["ac", "c"]
Bis auf wenige Ausnahmen (siehe unten) sind die beiden Funktionen gleich. Zurückgegeben wird ein Array mit dem gesamten passenden Teil und den im regulären Ausdruck eingeklammerten Teilen.
Prüfen mit RegExp.test(String)
// test: just booleans
/^abc$/.test("abc"); // true
/^abc$/i.test("AbC"); // true
/^abc$/.test("abcd"); // false
Diese Funktion ist zum Validieren von Eingaben recht praktisch, wenn es nicht auf den Inhalt des Strings ankommt.
Teilen mit String.split(RegExp)
// split: arrayify stings "a,b,c.d".split(/\.|,/) // ["a", "b", "c", "d"] "a,.b- c .d_e".split(/[.,-_]/) // ["a", "", "b", " c ", "d", "e"] "a,.b- c .d_e".split(/[.,-_\s]*/) // ["a", "b", "c", "d", "e"]
Das Teilen kann auch mit einem normalen String geschehen, aber mit einem RegExp lassen sich, wie in diesem Beispiel demonstriert erweitere Funktionen implementieren, wie mehrere mögliche Trennzeichen oder das kürzen von Whitespace und leeren Elementen.
Lokalisieren mit String.search(RegExp) und RegExpResult.index
// search: match position
"0123b56b".search(/b/g); // 4
"0123b56b".search(/x/g); // -1
// index: match position, too
/b/.exec("0123b56").index; // 4
"0123b56".match(/b/).index; // 4
// -> "g"-flag:global
/b/g.exec("0123b56b").index; // 4
"0123b56b".match(/b/g).index; // undefined
Interessant ist, dass match und global sich nicht vertragen. Irgenwie ist es schon eine rechte Eigenart dass sich gobale und nicht-globale Ausdrücke so startk unterscheiden, daher würde ich RegExp-Objekte nicht zu weit durch Methodenaufrufe schicken.
Ersetzen mit String.replace(RegExp,replacement)
// replace var string="ab-ghi-abc-hl-ac" string.match(regexp); // ["abc", "bc"] string.replace(regexp,"#")); // "ab-ghi-#-hl-ac"
Globales Finden und Ersetzen
// more global var regexp2=new RegExp(regexp.source,'g') string.replace(regexp2,"#"); // "ab-ghi-#-hl-#" string.match(regexp2); // ["abc", "ac"] // loop like that: regexp2.exec(string); // ["abc", "bc"] regexp2.exec(string); // ["ac", "c"]] regexp2.exec(string); // null regexp2.exec(string); // ["abc", "bc"]
Hier unterscheiden sich match() und exec()! Während match() ein Array der allen Treffern aus den kompletten Trefferstellen liefert, liefert exec(), wie beim nicht-globalen, pro Fundstelle je ein Array inclusive Klammerstellen. Um mit exec() alle Treffer zu erhalten, kann man z.B. eine while-Schleife verwenden.
Backreferences
// backreferences
/([ab])c\1/.test("aca"); // true
/([ab])c\1/.test("bcb"); // true
/([ab])c\1/.test("bca"); // false
// replacement backreference
// notice 1-9 limitation
"ll mm kk kx a Bb lL".replace(/([lkb])\1/gi,"$12")
"l2 mm k2 kx a B2 l2"
// beyond 1-9
"abcdefghijklmnopqrst".replace(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/i,"$13-$1-$13+++") // "m-a-m+++qrst"
"abcdefghijklmnopqrst".replace(/(.)(?:.)(?:.)(?:.)(?:.)(?:.)(?:.)(?:.)(?:.)(?:.)(?:.)(?:.)(.)(?:.)(?:.)(?:.)/i,"$2-$1-$13+++") // "m-a-a3+++qrst"
// get a $
"ab".replace(/(.)/,"$$1") // $1b
"ab".replace(/(.)/g,"$$1") // $1$1
// special backrefs:
"abcdefg".replace(/(d)e/,"[ $`-$1-$' (inst $&) ]") // "abc[ abc-d-fg (inst de) ]fg"
Richtig interessante Dinge lassen sich machen mit Backreferences. Grundsätzlich können doppelte Vorkommen von passenden Teilen geprüft werden. Beim Ersetzen sind sie essenziell für Umformatierungen.
Spezialitäten
// special firefox shortcut:
/a/("cba") // a
/a(.)/("acbab") // ["ac", "c"]
// Ein Bonbon:
/\\/("\\")//["\"]
// Noch eines:
function _(é) {var ì=Array,í=RegExp,è=["join","length","replace"],î=[],_
="\x31",_=_*_,ë=_+_+_*_;for (var i=ë;i>=_;i--) {î[î[è[_]]]=("((\\\x64)"+
(new ì(i)[è[_-_]]("\\"+(ë-i+_)*(_+_)))+")");}var î=new í(î[è[_-_]]("|"),
"\x67"),ï=function(){var I=arguments;for(var i=ë;i>=_;i--){var j=(_+_)*(
ë+_-i);if(I[j]) {return i+I[j];}}},ì="",í=_+ì;for (i=_-_;i<é;i++) {ì+=(i
+"\x20"+í+"\n");í=í[è[_+_]](î,ï);}return ì;}alert(_(9));
Der Firefox-Shortcut könnte zwar praktisch sein, wird aber nicht von vielen Browsern unterstützt, und ist auch ein bisschen zu abstrakt.
Letztere Funktion benutzt Backreferences sowohl im RegExp als auch im Ersetzungstext, zudem ersetzt sie Rekursiv und baut das RegExp dynamisch. Außerdem habe ich sie ein bisschen verschlüsselt, da die Berechnung der ausgegebenen Zahlenreihe eigentlich ein Rätsel ist. Wenn man allerdings weiß, dass ich RegExp verwende, fällt die Lösung leichter, und wenn man es gelöst hat, weiß man vielleicht auch warum es ein perfekter Vorwand ist, um sich etwas mit Regulären Ausdrücken zu beschäftigen.
Such-Wiki zu „javascript regexp“













