Powiadomienia SMS mBank mobile – drugie podejście
Dwa miesiące temu przedstawiłem dwa sposoby w jaki można sobie uruchomić powiadomienia SMS od aplikacji lub o przychodzących mailach. Oba miały pewne wady więc zdecydowałem się powrócić do tematu raz jeszcze.
Pierwszym rozwiązaniem, było wykorzystanie +48xxxxxxxxx@text.plusgsm.pl
Rozwiązanie dobre, bo dostajemy natychmiast powiadomienie na komórkę. Dodatkowo większość skrzynek pocztowych ma możliwość ustawienia automatycznej kopii na drugi adres – więc w dosłownie chwilę możemy sobie ustawić automatyczne powiadomienie o mailach.
Rozwiązanie to ma jednak swoją wadę – dostajemy pełną treść, która nie zmieści się w jednym SMS’ie jeśli w mailu jest jakakolwiek treść poza samym tematem.
Drugim zasugerowanym przeze mnie rozwiązaniem było wykorzystanie możliwości wysyłania powiadomień SMS z Google Calendar. To rozwiązanie również jest dobre. Możemy wykorzystać konto w Google Apps, dzięki czemu adres powiadomień jest bardziej uniwersalny, ale ma też swoje wady – chodzi mianowicie o dzienny limit powiadomień SMS z kalendarza Google. Jak się przekonałem, taki limit jest. Dokładnej liczby nie znam, ale w przybliżeniu to około 25 SMS’ów.
Rozwiązanie optymalne?
Postanowiłem zatem poszukać czegoś po środku. Wykorzystałem obsługę skrzynki Google do pobierania wiadomości, a następnie jedynie nadawcę i temat przekazuje do usługi text.plusgsm.pl. Wydaje mi się, że rozwiązanie jest optymalne. Oto kod:
#!/usr/bin/env python
# -- encoding: utf8 --
__author__ = 'mw AT nme.pl'
__version__ = '1.4'
__date__ = 'pią, 25 wrz 2009, 13:22:28 CEST'
class setup:
verbose = False
class google:
login = 'KONTO@GOOGLE'
password = 'HASŁO'
class notify:
recipient="+48xxxxxxxxx@text.plusgsm.pl"
import poplib
import email
import email.Header
#import sms
import smtplib
import re
class Fwd:
def __init__(self,address,subject):
mesg = "From: %s\nTo: %s\nSubject: %s\n\n" % (address, setup.notify.recipient, subject)
server = smtplib.SMTP('smsb.plusgsm.pl')
if setup.verbose:
server.set_debuglevel(1)
server.sendmail (address, setup.notify.recipient, mesg)
server.quit()
class Mailbox:
def __init__(self):
self.server = poplib.POP3_SSL('pop.gmail.com', 995)
self.server.user(setup.google.login)
self.server.pass_(setup.google.password)
self.server.set_debuglevel(0)
# fetch
count = self.server.stat()[0]
if setup.verbose:
print count
if count:
for i in range(1,count+1):
(header, msg, octets) = self.server.retr (i)
mail = email.message_from_string('\n'.join(msg))
address = email.Header.decode_header(mail['From'])[1]
if address[1]:
address = address[0].decode(address[1],'ignore')
else:
address = address[0]
address = re.sub('>\s*$','',re.sub('^\s*< ','',address))
subject = email.Header.decode_header(mail['Subject'])[0]
if subject[1]:
subject = subject[0].decode(subject[1],'ignore')
else:
subject = subject[0]
#print subject
#sms.parse(subject.split())
Fwd(address,subject)
#self.server.dele (i)
# commit
self.server.quit()
mbox = Mailbox()
Aby te powiadomienia działały, na koncie shellowym umieszczamy skrypt, nadajemy mu prawa do uruchomienia, przechodzimy do edycji crona:
chmod +x pop2smsV2.py crontab -e
dodajemy zadanie do crona, aby był uruchamiany co minutę:
* * * * * /home/NAZWA/bin/pop2smsV2.py >/dev/null 2>&1
Podsumowanie
Teraz to rozwiązanie najbardziej przypomina mi to które miałem w Erze. Trzeba niestety użyć do tego shella z cronem. OVH odpada z dwóch powodów – brak crona oraz brak dostępu do sieci z poziomu shella. Może i by się dało uruchomić to jako CGI z ich webowym cronem, ale niestety mija się to trochę z celem, ponieważ cron OVH może być uruchamiany najczęściej co godzinę.
Przewagą tego rozwiązania nad powiadomieniem z Ery jest brak limitu skrzynki 12 MB. Limitem jest ponad 7 GB co nie stanowi już problemu. Jeśli nie chcemy zostawiać na skrzynce śladu – możemy usunąć komentarz z linii self.server.dele (i) – co spowoduje kasowanie przesłanych jako SMS wiadomości.
Zaletą obsługi powiadomień za pomocą crona jest to, że możemy ustalić sztywne godziny w których skrypt ma działać – możemy ustawić, że powiadomienia mogą przychodzić pomiędzy 8:00, a 16:00 oraz 18:00 – 22:00 (trzeba się wtedy liczyć z faktem, że o ósmej rano otrzymamy wszystkie SMS’y z nocy). Jeśli chcemy mieć powiadomienia aktywne w godzinach 8:30 – 22:30, robimy to następującymi regułami:
30-59 8 * * * /home/nme/sms-notify/pop2smsV2.py >/dev/null 2>&1 * 9-21 * * * /home/nme/sms-notify/pop2smsV2.py >/dev/null 2>&1 0-30 22 * * * /home/nme/sms-notify/pop2smsV2.py >/dev/null 2>&1
Dodatkowo – dzięki temu rozwiązaniu mamy adres do powiadomień, znacznie łatwiejszy do zapamiętania – bo w postaci np. powiadomienia@nasza-domena.pl
Kontrolka button w jQuery UI
Ogólny trend rozwoju interfejsów aplikacji spycha je coraz bardziej w kierunku webowym. Zmieniają się dzięki temu developerzy i sposób postrzegania aplikacji. Lekkość aplikacji zaczyna być w cenie. Krowiaste applety Javy, przerośnięte Flashe zaczynają powoli ustępować lżejszym rozwiązaniom. Jednym z nich jest jQuery UI.
Czym jest jQuery UI
jQuery UI to mini-framework dla aplikacji po stronie klienta. Powstał on przy projekcie jQuery, a zespoły tych projektów są dość mocno zintegrowane – świadczy o tym m.in About jQuery, wspólna dokumentacja oraz tempo rozwoju. Zespół UI jest dodatkowo zrzeszony w Filament Group, pod szyldem którego publikuje całkiem często dość ciekawe artykuły bądź to opisując UI, Aria, bądź uzupełniając kontrolki o nowe i jednocześnie je omawiając. Na ich stronie nie znalazłem RSS’a, ale o nowościach zawsze wspominają na swoim Twitterowym profilu filamentgroup – którego kanał RSS sobie zasubskrybowałem w moim Google Readerze – rozwiązanie spisuje się dobrze
Za pomocą jQuery UI możemy przygotować graficzny interfejs naszej aplikacji webowej. Mamy do dyspozycji bibliotekę funkcji rozszerzających standardowe efekty o nowe, własne kontrolki, tematy graficzne (które możemy rozszerzać o własne) wraz z rewelacyjnie narysowanymi ikonkami, dostosowanymi do każdego z tematów. Żeby zobaczyć jak to wszystko wygląda w różnych standardowych tematach graficznych – przykładowy podgląd ThemeRoller.
Standardowa baza kontrolek (widget’ów) jest niewielka – kontrolka daty, okno dialogowe, progress bar, taby. Dodatkowych można się doszukać na SVN’ie w labsach albo poprzez wspomnianą wyżej stronę filamentgroup.
Wprowadzenie do jQuery UI w Twitterowym stylu
Do html’a naszej aplikacji załaczamy CSS z wybranego tematu jQuery UI oraz skrypty jQuery i jQuery UI – przykładowo:
<link href="theme/start/jquery-ui.css" type="text/css" rel="stylesheet"/>
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jquery-ui.js" type="text/javascript"></script>
Mamy teraz dostępną całą paletę styli i narzędzi jQuery UI. Zarówno jedno jak i drugie jest bardzo łatwo rozszerzalne.
Jak to rozbudować?
Stworzymy sobie przycisk (ang. button) – przyda się nam omówiona w poprzednim wpisie biblioteka do generowania dynamicznego CSS. Tak mniej więcej wygląda początkowy szkielet aplikacji:
$(document).ready(function() {
var self = this;
self.css = new CSS('dynamic');
//include('button.js');
});
Jak wszyscy wiemy, w JavaScript nie ma include. Można zrobić tak aby był, ale o tym napiszę niebawem. Póki co w jego miejsce możemy wstawić kod właściwej kontrolki.
// ui.button
self.css.replace('.cx-button',{
'outline':'0',
'margin':'0 4px 0 0',
'padding':'.4em 1em',
'text-decoration':'none !important',
'cursor':'pointer',
'position':'relative',
'text-align':'center',
'zoom':'1'
});
// remove extra button width in IE
self.css.replace('a.cx-button',{
'float':'left'
});
(function($) {
$.widget('ui.button', {
_init: function() {
this.element
.addClass('cx-button ui-state-default ui-corner-all')
.hover(function() {
if (! $(this).hasClass('ui-state-disabled')) {
$(this).addClass('ui-state-hover');
}
},function() {
$(this).removeClass('ui-state-hover');
})
.mousedown(function() {
if (! $(this).hasClass('ui-state-disabled')) {
$(this).addClass('ui-state-active');
}
})
.mouseup(function() {
$(this).removeClass('ui-state-active');
})
.disableTextSelect();
},
disabled: function() {
this.element.addClass('ui-state-disabled');
},
enabled: function() {
this.element.removeClass('ui-state-disabled');
},
destroy: function() {
this.element
.removeClass('cx-button ui-state-default ui-corner-all ui-state-hover ui-state-active ui-state-disabled')
.unbind('mouseenter mouseleave');
}
});
})(jQuery);
Takie właśnie przyciski zostały przeze mnie użyte na przykładowej stronie flatforma.appspot.com – należy najpierw przekliknąć się w accordeonie po lewej, a wtedy pojawią się przyciski, które dodają nowe zakładki. Dla leniwych zrzut ekranu:

Zachęcam do analizy przedstawionego w tym tekscie kodu – można go uznać za bazowy szkielet do tworzenia kontrolek.
Styling Buttons and Toolbars with the jQuery UI CSS Framework
Dynamiczny CSS z poziomu JavaScript
Jak zapewne większość developerów używających JavaScriptu, mam swoją własną biblioteczkę skryptów oraz dodatkowych narzędzi która stanowi dla mnie formę bootstrapu do tworzenia nowej aplikacji. Z każdym projektem biblioteczka rośnie i staje się bardziej dojżała. Przymierzam się od jakiegoś czasu do opublikowania całego bootstrapu, bo uważam, że jest warty uwagi, ale jakoś nie miałem do tej pory zbytnio na to czasu.
W tym tekscie poniżej przedstawie jeden ze skryptów który należy do mojej biblioteczki. Załączam go standardowo do każdej swojej aplikacji, szczególnie teraz, kiedy zacząłem przygotowywać swoje własne rozszerzenia JQuery UI.
Swobodniejsze podejście
Bardzo lubie dbać o to, aby struktura aplikacji była zwięzła, łatwa do ogarnięcia oraz estetyczna. W dobie Javascriptu mamy coraz to większe możliwości aby samemu sobie ustrukturyzować aplikacje.
Wielu doświadczonych developerów zakłada, sztywny podział – czyli, że wygląd powinien być w plikach CSS, funkcjonalność w JavaScript, a dane na serwerze. Oczywiście z trzecim się zgadzam, ale z podziałem na wygląd i funkcjonalność – już nie koniecznie.
Przykładowo – w naszym pliku CSS wykorzystujemy -moz-border-radius i -webkit-border-radius, dzięki czemu mamy zaokrąglone rogi wszędzie, poza Internet Explorerem. Musimy dodać zaokrąglone rogi w Explorerze. Co począć? Nabrudzić sobie w CSSie? A później za pomocą JQuery sprawdzić wersję przeglądarki i ustawić te klasy? Można i tak. Możemy jednak sprawdzić czy mamy do czynienia z IE i jeśli tak – dopiero wtedy dodać nowe klasy CSS.
Dynamiczne ładowanie lub generowanie CSS nie jest czymś bez czego nie możnaby się obyć, ale dzięki warstwom abstrakcji możemy sobie nie śmiecić w plikach z layoutem.
Poniższy kod działa bez problemu w każdej zaawansowanej przeglądarce WWW oraz w Internet Explorerze
/* * CSS Gen * Copyright (c) 2009 nme.pl * Dual licensed under MIT and GPL. * * from scratch, based on article http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript * * Example usage: * * var css = new CSS('dynamic'); // Create new stylesheet * css.replace('.asd',{'color':'green'}); // If there is no asd class definition yet, it will be created * */ var CSS = function(name,url) { if (name === undefined) { return false; } var styles = document.styleSheets; if (!styles) { return null; } this.find_style = function(name) { if (styles) { for (var i in styles) { if (styles[i].title === name) { return styles[i]; } } } return null; }; var create_style = function(name) { var node = document.createElement('style'); node.type = 'text/css'; node.rel = 'stylesheet'; node.media = 'screen'; node.title = name; document.getElementsByTagName(”head”)[0].appendChild(node); return node; }; var load_style = function(name,url) { var node = document.createElement('link'); node.type = 'text/css'; node.rel = 'stylesheet'; node.href = url; node.media = 'screen'; node.title = name; document.getElementsByTagName(”head”)[0].appendChild(node); return node; }; var style = this.find_style(name); if (style === null) { if (url === undefined) { style = create_style(name); } else { style = load_style(name,url); } } var sheet = null; if (style !== null) { if (style.styleSheet !== undefined) { sheet = style.styleSheet; } else { sheet = style.sheet; } } this.index = function(search) { search = search.toLowerCase(); var i=0; var rule=null; if (sheet === undefined) { return null; } do { if (sheet.cssRules) { rule = sheet.cssRules[i]; } else if (sheet.rules) { rule = sheet.rules[i]; } if (rule) { if (rule.selectorText.toLowerCase() == search) { return i; } i++; } } while (rule); return null; }; this.rule = function(search) { search = search.toLowerCase(); var i=0; var rule=null; if (sheet === undefined) { return null; } do { if (sheet.cssRules) { rule = sheet.cssRules[i]; } else if (sheet.rules) { rule = sheet.rules[i]; } if (rule) { if (rule.selectorText.toLowerCase() == search) { return rule; } i++; } } while (rule); return null; }; this.add = function(search, body) { if (sheet === undefined) { return null; } if (typeof body === 'object') { var rules=''; for (var i in body) { if (body.hasOwnProperty(i)) { rules = rules+i+':'+body[i]+';\n'; } } body = rules; } if (!this.rule(search)) { if (sheet.cssRules) { sheet.insertRule(search+' {'+body+'}', 0); } else if (sheet.rules) { sheet.addRule(search, body); } } return this.rule(search); }; this.remove = function(search) { var i = this.index(search); if (i !== null) { if (sheet.cssRules) { sheet.deleteRule(i); } else if (sheet.rules) { sheet.removeRule(i); } } }; this.replace = function(search, body) { this.remove(search); return this.add(search,body); }; };
Jak tego używać? Na początku przygotowujemy nowy, dynamiczny stylesheet:
var css = new CSS('dynamic');
Jeśli klasa asd nie została jeszcze zdefiniowana, robimy to teraz oraz ustawiamy jej następujące atrybuty:
css.replace('.asd',{'color':'green'});
Pseudoklasa CSS umożliwia również dynamiczne ładowanie CSS’ów czy usuwanie poszczególnych reguł styli. W razie wątpliwości – zachęcam do zaglądania do kodu oraz do kontaktu ze mną
Przykładową aplikacją, która dość intensywnie korzysta z tego fagmentu kodu jest http://mlchat.appspot.com – czat, który uruchomiłem na Google App Engine, który jednak zarzuciłem, bo nie znalazłem już sił po stworzeniu go na jego odpluskwianie i promocję
Wygląd aplikacji miał być z zamierzenia łudząco podobny do Google Web Toolkit‘u i co za tym idzie takich aplikacji jak Google Reader, ale tak naprawdę było to JQuery i trochę pracy
Zachęcam do poklikania – ludzi tam raczej nie ma, jak sugerowałyby liczby, to tylko duchy – stare sesje się nie kasują jak powinny
Niech ktoś to zrobi pod Windowsem :P
Wyobraźmy sobie, że surfujemy pod Windowsem, natrafiamy na coś takiego…

…a pod Linuxem wystarczy jeden skrót klawiszowy i mamy taki efekt:

Aby móc to robić potrzebny jest uruchomiony Compiz. Tą funkcjonalność (Dostępność / Negatyw) i inne najwygodniej skonfigurować, lub włączyć za pomocą System / Preferencje / Menadżer ustawień CompizConfig. Jeśli takowego nie posiadamy – możemy go sobie zainstalować następującą komendą:
sudo aptitude install compizconfig-settings-manager
Skróty negatyw danego okna lub negatyw ekranu są wbrew pozorom dość przydatne, zwłaszcza wieczorem, podczas czytania ebooków.
Aniołek pochodzi ze strony http://diox.mylog.pl
Instalacja Chromium w Ubuntu Jaunty
Przeglądarka WWW giganta z Mountain View o nazwie Google Chrome jest dostępna póki co jedynie dla Windows. Była ona tworzona w pośpiechu i tajemnicy. Kiedy została wydana, Google jednocześnie otworzyło otwartoźródłowy projekt o nazwie Chromium. Składają się na niego między innymi własny fork silnika do renderingu stron – WebKit czy stworzony przez Google – bardzo wydajny silnik Javascriptu o nazwie V8. Dzięki otwarciu kodu, umożliwiony został szybszy rozwój oraz poprawę tego, co było zrobione na szybko oraz tego, czego najbardziej brakuje. Do projektu Chromium dołączyło wielu developerów spoza Google, którzy bardzo aktywnie zaczęli podrzucać łaty, mające na celu umożliwienie uruchamianie Chrome pod Linuxem. Prace się jeszcze nie skończyły, ale już teraz możemy uruchomić wersję developerską – Chromium, na własnym Linuksie. W tym tekscie pokażę, jak tego dokonać pod Ubuntu 9.04.
Dlaczego chcę o tym pisać, kiedy lada dzień ma wyjść Ubuntu 9.10? Z przyczyn archiwalnych, ponieważ w Ubuntu 9.10 będzie się to robiło już inaczej, znacznie prościej. Nie oznacza to, że jeśli zainstalujemy teraz Chromium, będziemy musieli wykonywać nowe kroki po aktualizacji do 9.10 – Chromium będzie działał jak dawniej.
# Najpierw przechodzimy na roota, tego którego w Ubuntu nie masudo -i # Dodajemy repozytorium Chromium z Ubuntu Launchpad PPA cat >/etc/apt/sources.list.d/chromium.list << EOF deb http://ppa.launchpad.net/chromium-daily/ppa/ubuntu jaunty main deb-src http://ppa.launchpad.net/chromium-daily/ppa/ubuntu jaunty main EOF # Dodajemy klucz do pakietów z repozytorium Chromium gpg --keyserver hkp://wwwkeys.eu.pgp.net --recv-keys 5A9BF3BB4E5E17B5 gpg --export --armor 5A9BF3BB4E5E17B5 | sudo apt-key add - # Aktualizujemy repozytoria i instalujemy Chromium wraz z wersją Polską
aptitude update aptitude install -y chromium browser chromium-browser-l10n
Po pierwszym uruchomieniu, Chromium pyta o ustawienie go jako domyślnej przeglądarki oraz import z Firefoxa - ustawienia jako domyślnej przeglądarki póki co nie polecam, a import u mnie to jeszcze nie działa.
Żeby było bardziej pod kolor, warto nacisnąć Ustawienia, Opcje, zakładka Prywatne rzeczy, Motywy: Ustaw motyw na GTK+.
Obsługa Flasha i innych wtyczek jest narazie eksperymentalna, ale mnie przynajmniej nic się nie jeszcze zawiesiło - YouTube można oglądać. Aby działał Flash, skrót do Chromium należy uruchamiać z parametrem --enable-plugins:
chromium-browser --enable-plugins
Przeglądarka działa lekko i przyjemnie - w moim odczuciu - o wiele lżej niż Firefox, którego ostatnio coraz bardziej nie mogę znieść - nie ważne czy to pobranego bezpośrednio ze stron Mozilli, czy tego z repozytoriów Ubuntu. Chromium jest też w moim odczuciu stabilniejszy niż Midori, którego do tej pory używałem do testowania moich aplikacji webowych pod kątem WebKit'a pod Linuxem. Spróbujcie, warto.
