Tag: Ruby

Paperclip – walidacja rozmiaru obrazków(grafik) – podejście drugie

Nie tak dawno temu opublikowałem wpis w którym pokazuję jak zrobić walidację minimalnego rozmiaru obrazka z wykorzystaniem Paperclipa. Podejście to było (i jest) proste, jednak okazało się niewystarczające (niewygodne) gdy mamy do czynienia z kilkoma plikami graficznymi na model. Z tego względu przebudowałem tę metodę, tak aby wydzielić ja do samego Paperclipa. Mimo że wyszła wersja 2.3.8 Paperclipa, sam korzystam z lekko zmodyfikowanej przeze mnie 2.3.6, która działa na Rails3. Kod ten powinien działać na każdej wersji Paperclipa.

module Paperclip
  module ClassMethods
    def validates_attachment_minimum_resolution name, options = {}
      validation_options = options.dup
      validates_each(name, validation_options) do |record, attr, value|
        unless record.errors.include?(name)
          m_width  = options[:width]
          m_height = options[:height]
          message = options[:message] || "must be bigger."
          message = message.call if message.is_a?(Proc)
          image = record.send(name)
          if image && image.queued_for_write[:original]
            dimensions = Paperclip::Geometry.from_file(image.queued_for_write[:original])
            if dimensions.width < m_width || dimensions.height < m_height
              if record.errors.method(:add).arity == -2
                record.errors.add(:"#{name}", message)
              else
                record.errors.add(:"#{name}", :inclusion, :default => options[:message], :value => value)
              end
            end
          end
        end
      end
    end
  end
end

Wrzucamy do /lib/extensions, podpinamy w /config/application.rb:

require 'extensions/paperclip'

I używamy. A jak działa? Załóżmy że mamy mieć miniaturkę (:thumb), która ma mieć rozdzielczość minimum 300x300 i być w formacie JPG:

  TYPES = ['image/jpeg', 'image/jpg', 'image/pjpeg']
  FILE_MAX_SIZE = 2048

  has_attached_file :thumb, :styles => {:mini => "120x95\#",
                  :standard => "190x150\#", :big => "300x300>"},
                  :url => "/images/models/thumb/:id_:style.:extension",
                  :default_style => :standard

  validates_attachment_presence :thumb,
    :message =>  'musi być dołączony'
  validates_attachment_size :thumb, :less_than => FILE_MAX_SIZE.kilobytes,
    :message =>  "musi mieć mniej niż #{FILE_MAX_SIZE} kb"
  validates_attachment_content_type :thumb, :content_type => TYPES,
    :message =>  "ma nieprawidłowy format"
  validates_attachment_minimum_resolution :thumb,
    :width => 300, :height => 300,
    :message => "mus mieć minimum 300px na 300px"

Ważne jest aby sprawdzać typ pliku przed sprawdzeniem rozmiarów, ponieważ walidacja ta nie uruchomi się jeśli plik zostanie uznany przez wcześniejsze walidacje za niepoprawny.

Warto zauważyć, że można to internacjonalizować. Mamy w kodzie taki fragment:

message = message.call if message.is_a?(Proc)

Dzięki któremu możemy zrobić w kodzie tak:

:message => lambda{I18n.t("activerecord.errors.messages.paperclip.res", {
    :width => 300, :height => 300})}

I mieć inny komunikat zależnie od języka.

Warto wspomnieć też, że walidacja ta (tak jak reszta paperclipowych) przeprowadzana jest tylko jeśli wymuszamy podstawową walidację obecności pliku:

  validates_attachment_presence :thumb

Aby uruchamiać walidację tylko jeśli plik został do formularza dołączony (ale formularz może być bez pliku), można zrobić tak:

  validates_attachment_presence :thumb,
    :message =>  "musi istnieć",
    :if => Proc.new { |imports| imports.thumb.file? }

Lub, można też dopisać sobie taką metodę do naszego rozszerzenia i będzie to za nas robione automatycznie:

    def validates_attachment_if_included name, options = {}
      options[:if] = Proc.new { |imports| imports.send(name).file? }
      validates_attachment_presence name, options
    end

Przykład:

  validates_attachment_if_included :thumb

Paperclip minimum resolution validation extension

Poprawne linki z Facebooka dla JWPlayera – wygasające linki – część I

Wstęp

Tutorial składa się z dwóch cześci:

  1. FacebookBot z wykorzystaniem Mechanize
  2. Cachowanie odpytań

Kod (nowszy i lepszy ;) ) dostępny na githubie.

Część z Was na pewno osadza swoje filmiki z konta Facebook także na swoich stronach www. Niektórzy robią to z pomocą odtwarzacza dostarczonego przez Facebooka, inni korzystają np. z JWPlayera. Niestety ostatnimi czasy, Facebook zauważył, że staje się bardzo fajną platformą hostingową dla filmów wszelakich (i to w jakości HD!). Zjadało im to (i zjada ;) ) gigantyczne ilości zasobów - przede wszystkim łącza. Jeśli osadzasz pliki wideo przez ich odtwarzacz - to nie ma problemu - FB ma z tego korzyści (mają swoje logo na filmiku, itd). Korzystając jednak z JWPlayera - nie uda ci się ta sztuczka. Linki bezpośrednie do plików MP4 są zmieniane co 24-36 godzin, w skutek czego umieszczanie ich "w" odtwarzaczu nie ma sensu. Na szczęście da się to bardzo łatwo rozwiązać, jak zawsze w... Rubym :)

Mechanize

Napiszemy prostego bota - który wchodzi na Facebooka, loguje się i sprawdza URL filmu. Warto dodać cacheowanie, tak aby nie odpytywać FB za każdym razem o to samo. Rozbudowę tego narzędzia pozostawię jednak Wam. Wracając do sedna sprawy. Aby napisać tego bota, skorzystamy z Mechanize. Mechanize jest biblioteką stworzoną do łatwej integracji ze stronami wszelakimi. Umożliwia przesyłanie formularzy, odwiedzanie stron, zapewnia obsługę cookies, ssl-a, itp, itd. To głównie dzięki niemu, będziemy mogli odświeżać linki do plików MP4 z FB.

Zanim jednak to zrobimy, musimy dodać do Mechanize pewną małą poprawkę. Domyślnie nie pozwala wyszukiwać formularzy po ID a nam taka metoda się przyda. Tak więc:

class Mechanize::Page
  def form_id(formId)
    formContents = (self/:form).find { |elem| elem['id'] == formId }
    if formContents then return Mechanize::Form.new(formContents) end
  end
end

Tyle :) A teraz pora na FacebookBota.

FacebookBot

Nasz bot będzie miał łącznie 4 metody:

  1. Initialize - inicjalizacja bota
  2. Login - logowanie do Facebooka
  3. Video_Url - pobranie URLa pliku wideo
  4. (priv) get_url - wyodrębnienie samego urla pliku ze strony z linkiem

Szkielet klasy będzie więc wyglądał tak:

require 'rubygems'
require 'mechanize'
require 'uri'
require 'cgi'
require 'time'

class FacebookBot
  # Strona główna Facebooka
  FB_URL = "http://www.facebook.com/"
  # Nasza przeglądarka i system :)
  USER_AGENT = 'Linux Firefox'

  def initialize(email, pass)
  end

  def login
  end

  def video_url(video_id)
  end

  private

  def get_url(url)
  end
end

Inicjalizacja naszego bota składa się z zapamiętania e-maila i hasła, utworzenia obiektu Mechanize do eksploracji FB oraz próby zalogowania na nasze konto:

  def initialize(email, pass)
    @email, @pass = email, pass

    @agent = Mechanize.new
    @agent.user_agent_alias = USER_AGENT

    @cookies = File.dirname(__FILE__) + "/../cookies-" + @email + ".yml"
    if (File.file?(@cookies))
      @agent.cookie_jar.load(@cookies)
    end

    self.login
  end

Wartym omówienia jest ten fragment:

    @cookies = File.dirname(__FILE__) + "/../cookies-" + @email + ".yml"
    if (File.file?(@cookies))
      @agent.cookie_jar.load(@cookies)
    end

Zmienna @cookies trzyma ciastka sesji od Facebooka na dysku. Dlaczego jest o katalog wyżej (../) niż sam plik? Ponieważ plik z botem wrzucimy do 'lib/' a plik z ciastkiem będziemy chcieli mieć w katalogu głównym naszej małej aplikacji. Tak więc, ustalamy sobie ścieżkę, sprawdzamy czy plik już istnieje i jeśli tak jest, to ładujemy jego zawartość do kontenera na ciacho - tak żeby można się było przedstawiać nim w Facebooku. Następnie podejmujemy próbę logowania.

Pora na logowanie:

  def login
    page = @agent.get(FB_URL)

    if (loginf = page.form_id("login_form"))
      loginf.set_fields(:email => @email, :pass => @pass)
      page = @agent.submit(loginf, loginf.buttons.first)
    end

    @agent.cookie_jar.save_as(@cookies)

    body = page.root.to_html
    @uid = %r{\\"user\\":(\d+),\\"hide\\"}.match(body)[1]
    @post_form_id = %r{<input type="hidden" id="post_form_id" name="post_form_id" value="([^"]+)}.match(body)[1]
  end

Logowanie przebiega w następujący sposób:

  1. Próbujemy przejść na stronę główną FB.
  2. Jeśli się to udało - tzn że nasze ciastka były i były prawidłowe
  3. Jeśli nie, to wypełniamy formularz logowania i klikamy na "Zaloguj"
  4. Zapamiętujemy uaktualnione ciastka
  5. Przechodzimy na stronę główną i zapamiętujemy id usera (UID)
  6. Zapamiętujemy klucz do przesyłania żądań typu POST

Prawda że proste? :)

Video_url:

def video_url(id)
    begin
      pa = @agent.get("#{FB_URL}video/video.php?v=#{id}")
      pr = pa.body
      get_url(pr)
    rescue
      "Niepoprawne ID pliku wideo lub wideo nie jest publiczne."
    end
end

Pobieramy stronę pliku wideo, wyciągamy link do MP4 i zwracamy. Jeśli coś pójdzie nie tak, to zwracamy info że wideo jest nieprawidłowe lub nie jest publiczne.

Samo parsowanie przebiega w sposób trochę "brutalny" - nie chciało mi się bawić i myśleć to wyciągnąłem tak niezbyt elegancko:

private

def get_url(url)
	url = url.scan(/addVariable\(\"highqual_src\",\s\"http.+\"\)/ix).first
	url = url.split(')').first.gsub('\u00253A', ':')
	url = url.gsub('\u00252F', '/')
	url = url.gsub('\u00253F', '?')
	url = url.gsub('\u00253D', '=')
	url = url.gsub('\u002526', '&')
	url = "http://#{url.split('http://')[1]}".split('"').first
	CGI.unescapeHTML(url)
end

Grunt że działa.
Oto jak działa całość:

  fb = FacebookBot.new('moj@mail.pl', 'moje_tajne_haslo')
  p  fb.video_url(ID_PLIKU_VIDEO)

Warto dodać do tego system cacheowanie, tak aby nie odpytywać FB za każdym razem o to samo!

Źródełko :)

W związku z szerokim zainteresowaniem, udostępniam prosty interfejs: www.fbc.mensfeld.pl. Linki do odcinków buduje się tak:

www.fbc.mensfeld.pl/ID-ODCINKA.mp4

Tutorial składa się z dwóch cześci:

  1. FacebookBot z wykorzystaniem Mechanize
  2. Cachowanie odpytań

Kod (nowszy i lepszy ;) ) dostępny na githubie.

Copyright © 2026 Closer to Code

Theme by Anders NorenUp ↑