Dagens sitat via SMS


fredag 22. januar 2010 PSWinCom SMS Ruby

Jeg fortsetter å rote med Ruby, og deler villig med alle som har lyst til å lære. Denne gangen har jeg laget et lite skript som henter et daglig oppdatert visdomsord fra nettet, og sender det til en distribusjonsliste på SMS via PSWinCom's SMS Gateway. Programmet illustrerer hvor enkelt det er å hente og parse en RSS-feed, bruk av konfigurasjonsfiler i YAML-format, og sending av XML til en server over TCP (ett av PSWinCom's mange integrasjonsgrensesnitt).

ruby_quote

Det komplette programmet er listet ut nedenfor. La meg kjapt gå gjennom de ulike delene:

Metoden get_quote (linje 5 til 11) tar inn URL'en til en RSS-feed fra BrainyQuote som gir meg ett nytt sitat hver dag. Jeg åpner en connection mot adressen, og parser den med RSS-parser som er en del av Ruby's grunninstallasjon. Jeg plukker ut det første elementet, slår sammen beskrivelsen (selve sitatet) og tittelen (hvem sitatet stammer fra), og returnerer dette (siste statement i en ruby-metode returneres automatisk).

Metoden get_sms_xml (linje 13 til 28) tar inn nødvendige parametre og bygger opp XML'en jeg skal sende til SMS Gateway'en. Dokumentasjon av dette formatet er definert i denne pdf'en. I metoden bruker jeg noe som kalles for et Here-Document – en "tradisjonsrik" måte å definere strenger over flere linjer på som du også finner i PHP, Perl, Python, Unix shells, Windows Powershell etc.

Metoden send_sms (linje 30 til 34) åpner en TCP socket på en gitt adresse og port, sender XML'en, og avslutter. Det er alt som skal til for å sende SMS gjennom systemet vårt.., så sant du har en konto vel-å-merke.

QuoteSMS.rb:

1 require 'rss'
2 require 'open-uri'
3 require 'yaml'
4
5 def get_quote uri
6   open(uri) do |con|
7     rss = RSS::Parser.parse(con.read, false)
8     quote = rss.items.first
9     "#{quote.description} -#{quote.title}"
10   end
11 end
12
13 def get_sms_xml gw, receivers_xml, text
14 <<EOF
15 <?xml version="1.0"?>
16 <SESSION>
17 <CLIENT>#{gw[:client]}</CLIENT>
18 <PW>#{gw[:password]}</PW>
19 <MSGLST>
20 <MSG>
21 <TEXT>#{text}</TEXT>
22 #{receivers_xml}
23 <SND>#{gw[:sender]}</SND>
24 </MSG>
25 </MSGLST>
26 </SESSION>
27 EOF
28 end
29
30 def send_sms gw, xml
31   session = TCPSocket.new(gw[:host], gw[:port])
32   session.write xml
33   session.close
34 end
35
36 settings = YAML.load_file('settings.yml')
37
38 quote = get_quote settings[:quote_uri]
39 xml = get_sms_xml(settings[:gateway],
40                   settings[:receivers].map {|r| "<RCV>#{r}</RCV>"},
41                   quote)
42 send_sms settings[:gateway], xml

I linje 36 leser jeg inn konfigurasjonsdata (mer om dette om litt). Deretter er det bare å kalle de tre metodene, og hey-presto(!) så er jobben gjort. Nå kan jeg for eksempel sette opp en scheduled task til å kjøre dette programmet hver dag, og "spamme" mine venner med gode sitater på telefonen.

Legg merke til linje 40 som konverterer en array av telefonnumre til en XML streng array vha. metoden map. Dette tilsvarer å bruke Select-metoden i Linq i .NET:

    1 var list = new long[] { 4700000001, 4700000002, 4700000003 };

    2  IEnumerable<String> xml = list.Select(phonenumber

    3      => string.Format("<RCV>{0}</RCV>", phonenumber));

Det at jeg kan skyte inn arrayet i en annen streng som i linje 22, og at elementene da "flates ut" til én streng er minst like kult. Tilsvarende får du ikke til i .NET uten å eventuelt endre definisjonen av ToString() for enumerasjonen det gjelder. Let's not go there…

YAML Ain't Markup Language

Som sagt har jeg valgt å bruke YAML som format på konfigurasjonsfilen til dette programmet. YAML er et serialiseringsformat som er tilgjengelig for mange programmeringsspråk. Fordelen er at det er mye mere lesbart og editeringsvennlig enn XML, som har vært det mest populære formatet i noen år nå. YAML har mer til felles med JSON.., I praksis er JSON faktisk et subset av YAML. 

settings.yml:

1 ---
2 :gateway: 
3   :port: 9876
4   :client: demoAccount
5   :password: somePassw0rd
6   :host: 127.0.0.1
7   :sender: RubyQuote
8 :quote_uri: http://feeds.feedburner.com/brainyquote/QUOTEBR
9 :receivers:
10   - 4790000001
11   - 4790000002
12   - 4790000003

Denne settingsfilen definerer en Hash (el. dictionary om du vil) med tre nøkler med tilhørende verdier. Som nøkler har jeg brukt symboler – de tingen som begynner med kolon i Ruby – men strenger eller andre datatyper hadde fungert like fint. Nøkkelen :gateway har en ny Hash som verdi, som definerer settingene for å kontakte og bruke PSWinCom gatewayen. :sender gir for eksempel navnet jeg har valgt på avsenderen (se bildet av mobilen tidligere i artikkelen). Og nei, dette er ikke reelle brukernavn og passord…

Nøkkelen :quote_uri refererer til en streng som inneholder RSS-adressen. :receivers refererer til en array med telefonnummer. Når denne filen parses får jeg tilbake en Hash med alle disse verdiene i korrekte typer.

YAML er heller ikke begrenset til primitive typer som streng, arrays osv – man kan også serialisere og deserialisere Ruby-objekter man selv definerer, uten at det ser spesielt mer komplisert ut enn filen over. Faktisk trenger det egentlig ikke eksistere noen definisjon av objektene i programmet i det hele tatt – YAML-parseren vil konstruere nye typer om det trengs basert på det som er definert i YAML-filen. Ruby er jo tross alt et dynamisk språk. Ganske fiffig faktisk!

Konklusjon

Du har nå sett hvor enkelt det er å hente og parse en RSS feed. Du har også sett hvor smertefritt det er å sende SMS vha litt XML og PSWinCom's SMS Gateway. Og hvis ikke du fikk lyst til å bytte ut alle XML-konfigurasjonsfilene dine med YAML så har jeg kanskje forklart litt dårlig hvor kult det er.

Objektorienteringen er kanskje ikke så mye å skryte av, men for et program på 42 linjer ville det bare ha vært i veien. Håper dette inspirerte!

Se også: Send SMS med SOAP for hvordan du enkelt kan bruke PSWinCom's SOAP-grensesnitt for sending i .NET.


comments powered by Disqus