четвер, 12 березня 2009 р.

Ruby NoName Podcast

Сьогодні вийшов перший, вже не пілотний, випуск російськомовного подкасту про Ruby і технології пов'язані з ним Ruby NoName Podcast.
Хлопці (Дмитро також відомий як labria та Григорій також відомий як green_mouse) обіцяють виходити раз в тиждень, скоріш за все по п'ятницях або суботах.
Підписатися на RSS можна тут.
Залишається тільки побажати хлопцям натхнення і побільше цікавих подкастів.

Ruby + User API Vkontakte. Частина 3

28 жовтня 2008 Павло Дуров заявив про відкриття нового проекту: User API.
Сервіс призначений для швидкої побудови соціальної мережі або будь-яких інших клієнтів (цього на сторінці не заявлено).
Нові проекти будуть використовувати дані мережі ВКонтакте (користувачі, фото, т.п.).
Докладна інформація і документація по User API ВКонтакте доступна на англійською і російською мовами.
API побудований на обміні даними між клієнтом і сервером у вигляді HTTP-запитів, і відповіді від сервера клієнту у вигляді JSON-масиву в кодуванні UTF-8. Підтримується пакування GZIP. Передача даних клієнту, як правило, передбачає авторизацію клієнта в системі за допомогою передачі серверу ідентифікатора сесії('SiD') при будь-якому запиті даних.

Від сухої теорії до практики.

Напишемо простий скрипт:

  • Авторизація через надсилання email-адреси та пароля користувача;

  • Отримання інформації профілю;

  • Отримання списку друзів користувача, які в даний момент онлайн на сайті.


Будемо використовувати вже знайомі з мої попередніх постів бібліотеки WWW::Mechanize і JSON.
Створимо клас VkontakteAPI і методи login, get_user_page, get_friends_online_list.

Невеликий нюанс. Після успішної авторизації, на запит "http://userapi.com/data?act=profile&id=#{id}&sid=#{sid}", сервер повертає JSON на кшталт
[...]
{0: "xxxxxxxx_", 1: xxxxxxxx, 2: 1, 3: "First Last", 4: null, 5: "", "ts": 346000002}
[...]

Згідно специфікіції: строка - це набір символів Unicode, взятих у подвійні лапки А тут - без повдійних лапок. Парсер Ruby JSON видає помилку. Використаємо хак для виправлення цього у функції hack_json.

# -*- encoding: utf-8 -*-
#
# For more documentation see http://userapi.com

require 'json'
require 'mechanize'

class VkontakteAPI
def initialize
@sid = nil
@id = nil
@agent = WWW::Mechanize.new{|agent|
agent.user_agent_alias = 'Linux Konqueror'
}
end

# Authorisation by submitting email and password (Login)
#
def login(email, pass)
url = "http://login.userapi.com/auth?login=force&site=2&email=#{email}&pass=#{pass}"
login_page = @agent.get(url)
if !@agent.cookies.nil?
@id = /remixmid=(\d+)/.match(@agent.cookies[0].to_s)[1]
@sid = /.*;sid=(\w*)/.match(login_page.uri.to_s)[1]
return true
else
return false
end
end

# Getting user's profile information
#
def get_user_page(id = @id)
url = "http://userapi.com/data?act=profile&id=#{id}&sid=#{@sid}"
user_info_page = @agent.get(url)
JSON.parse(hack_json(user_info_page.body))
end

# List of online friends of a user
#
def get_friends_online_list(id = @id, from=0, to=5000)
url = "http://userapi.com/data?act=friends_online&from=#{from}&to=#{to}&id=#{id}&sid=#{@sid}"
friends_list_page = @agent.get(url)
JSON.parse(friends_list_page.body)
end

# little User API JSON hack :)
# Name property must be a String wrapped in double quotes
#
def hack_json(json_s)
json_s.gsub(/(\d+):/, '"\1":')
end

private :hack_json

end

if __FILE__ == $0
email = ARGV[0]
pass = ARGV[1]

vk = VkontakteAPI.new
if vk.login(email, pass)
puts vk.get_user_page
puts "Online friends:"
vk.get_friends_online_list.each do|friend|
puts "#{friend[0]}: #{friend[1]}"
end
else
"Error"
end
end

понеділок, 9 березня 2009 р.

Eval і Ruby

У Ruby (та інших скриптових мовах) є метод eval, який отримує рядок і виконує цей рядок в поточному контексті
так (майже так), як ніби він був написана програмістом в місці виклику eval.
Власне, засоби, подібні eval, є і в компільованих мовах програмування.

Приклад для Ruby:
eval("puts 'Hello World'")

Вивід:
Hello World


Повертаючись до попереднього посту зробимо деякі зміни у коді.
Маємо метод, який повертає кількість нових повідомлень.
def new_messages
get_feed['messages']['count']
end


Аналогічно можна написати для фотографій(photos) і відео(videos)...
Керуючись філософською думкою DRY підемо іншим шляхом.
Замість того щоб писати 9 майже однакових методів, застосуємо силу eval.
[:friends, :messages, :events, :groups, :photos, :videos, :notes, :opinions, :offers].each do |type|
eval(<<-eomethod)
def new_#{type}
get_feed['#{type}']['count']
end
eomethod
end

Таким чином ми згенерували 9 нових методів для класу Vkontakte.

пʼятниця, 6 березня 2009 р.

Mechanize + вКонтакте. Частина 2

Тихо і непомітно вийшов mechanize 0.9.2 у якому виправлений неприємний баг з Iconv.

Продовжим розбиратися з vkontakte.ru і бібліотекою mechanize. У попередньому дописі ми парсили веб-сторінку
і зчитували кількість нових повідомлень і фотографій. Така реалізація є незручною з багатьох причин, хоча і має право на існування. Розробники сервісу надали зручнішу річ для перегляду новин про профіль(http://vkontakte.ru/feed.php і http://vkontakte.ru/feed2.php).
http://vkontakte.ru/feed.php при відкритті окремо видає щось типу:
{"user": {"id": номер}, "friends": {"count": 0}, "messages": {"count": 0}, "events": {"count": 0}, "groups": {"count": 0}, "photos": {"count": 0}, "videos": {"count": 6}, "notes": {"count": 0}, "opinions": {"count": 0}, "offers": {"count": 0}}


Мова піде про JSON.

Wikipedia каже про нього наступне:

JSON (англ. JavaScript Object Notation, укр. об'єктний запис JavaScript, вимовляється джейсон) — це легкий формат обміну даними між комп'ютерами. JSON базується на тексті, і може бути з легкістю прочитаним людиною. Формат дозволяє описувати об'єкти та інші структури даних. Цей формат головним чином використовується для передачі структурованої інформації через мережу (завдяки процесу, що називають серіалізацією).

JSON знайшов своє головне призначення у написанні веб-програм, а саме при використанні технології Ajax. JSON виступає як заміна XML під час асинхронної передачі структурованої інформації між клієнтом та сервером. При цьому перевагою JSON перед XML є те, що він займає менше місця і прямо інтерпретується за допомогою JavaScript в об'єкти.



JSON - реалізації JSON для Ruby.
В Ruby 1.9.1 JSON входить в офіційне дерево вихідних кодів.

Нище вдосконалений скрипт із моєї попередньої статті.
Тепер крім зчитування кількості нових повідомлень, він ще зчитує кількість друзів, та друзів онлайн.
Хоча Mechanize і є емулятором веб-браузера, він не вміє(і наврядчи колись навчиться) виконувати JavaScript. А саме ним формується список друзів на сторінці http://vkontakte.ru/friend.php. У функціях friends_list і online_friends_list ми просто парсимо JavaScript на HTML-сторінці.
<script>friendsInfo [...] </script>


Скрипт протестований на Ruby 1.9.1 і Mechanize 0.9.2.
# -*- encoding: utf-8 -*-

require "json"
require "rubygems"
require "mechanize"

class Vkontakte

def initialize
@login = false
@a = WWW::Mechanize.new {|agent|
agent.user_agent_alias = 'Linux Konqueror' # WWW::Mechanize::AGENT_ALIASES
agent.follow_meta_refresh = true # VKontakte refresh after login
}
end

def online?; @online; end

# Sing in http://vkontakte.ru/
# Return false if error
#
def login!(email, pass)
@a.get('http://vkontakte.ru/login.php') do |login_page|
login_page.form_with(:name => 'login') do |form|
form.email = email
form.pass = pass
end.submit
if get_feed['user']['id'] == -1
@online = false
return false
else
@online = true
return true
end
end
end

def logout!
@a.get('http://vkontakte.ru/login.php?op=logout') do |page|
if get_feed['user']['id'] == -1
@online = false
return true
else
return false
end
end
end

# Return Hash of friend's uid => [first_name, last_name]
#
def friends_list
@a.get('http://vkontakte.ru/friend.php') do |friends_page|
friends = Hash.new
friends_page.body.scan(/\[(\d+?), \{f:'(.+?)', l:'(.+?)'\},\{p:.*?\}\]/mi).map{|item| friends[item[0]]=[item[1], item[2]]}
return friends
end
end

def online_friends_list
@a.get('http://vkontakte.ru/friend.php?act=online') do |friends_page|
friends = Hash.new
friends_page.body.scan(/\[(\d+?), \{f:'(.+?)', l:'(.+?)'\},\{p:.*?\}\]/mi).map{|item| friends[item[0]]=[item[1], item[2]]}
return friends
end
end

# Get JSON page and convert it to Hash object
#
def get_feed
@a.get('http://vkontakte.ru/feed2.php?mask=ufmepvnoq') do |feed_page|
return JSON::parse(feed_page.body)
end
end

def new_messages
get_feed['messages']['count']
end

private :get_feed

end


vk = Vkontakte.new
vk.login!(ARGV[0], ARGV[1])
if vk.online?
flist = vk.friends_list
oflist = vk.online_friends_list
puts "Friends: #{flist.size}"
puts "Online friends:"
oflist.each{|fid, fname| puts "#{fid}: #{fname[0]} #{fname[1]}"}
puts "New messages: #{vk.new_messages}"
vk.logout! ? "Good bye!" : "Ooops..."
else
puts "Such an e-mail address is not registered or an incorrect password is entered"
end