├── public ├── favicon.ico ├── empty.png ├── snap_chop.png ├── ajax-loader.gif ├── didntfindshit.jpg ├── copyrightcomplaint.jpg └── images │ └── faviconico.jpg ├── config ├── unicorn.rb └── newrelic.yml ├── .gitignore ├── Procfile ├── spec ├── support │ ├── test_image.jpg │ └── bing.json ├── sources │ ├── spice_spec.rb │ ├── bing_spec.rb │ └── image_iterator_spec.rb ├── actions │ ├── image_spec.rb │ └── noun_spec.rb └── fyn_spec.rb ├── boot.rb ├── actions ├── image │ └── didntfindshit.rb ├── noun │ ├── black_listed.rb │ └── nsfw.rb ├── noun.rb ├── shirt.rb └── image.rb ├── sources ├── image_iterator.rb ├── bing.rb └── spice.rb ├── Gemfile ├── config.ru ├── fyn.rb ├── Gemfile.lock └── views ├── home.rhtml └── noun.rhtml /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/unicorn.rb: -------------------------------------------------------------------------------- 1 | worker_processes 5 2 | timeout 30 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public/images/* 2 | log/* 3 | *.sqlite3 4 | *.db 5 | *.swp 6 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb 2 | -------------------------------------------------------------------------------- /public/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burke/fuckyeahnouns/HEAD/public/empty.png -------------------------------------------------------------------------------- /public/snap_chop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burke/fuckyeahnouns/HEAD/public/snap_chop.png -------------------------------------------------------------------------------- /public/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burke/fuckyeahnouns/HEAD/public/ajax-loader.gif -------------------------------------------------------------------------------- /public/didntfindshit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burke/fuckyeahnouns/HEAD/public/didntfindshit.jpg -------------------------------------------------------------------------------- /public/copyrightcomplaint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burke/fuckyeahnouns/HEAD/public/copyrightcomplaint.jpg -------------------------------------------------------------------------------- /public/images/faviconico.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burke/fuckyeahnouns/HEAD/public/images/faviconico.jpg -------------------------------------------------------------------------------- /spec/support/test_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burke/fuckyeahnouns/HEAD/spec/support/test_image.jpg -------------------------------------------------------------------------------- /boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | 4 | require 'open-uri' 5 | require 'json' 6 | require 'cgi' 7 | require 'RMagick' 8 | require 'sinatra/base' 9 | require 'timeout' 10 | require 'newrelic_rpm' 11 | require 'rest_client' 12 | -------------------------------------------------------------------------------- /actions/image/didntfindshit.rb: -------------------------------------------------------------------------------- 1 | module Actions 2 | class Image 3 | module Didntfindshit 4 | 5 | def file 6 | File.new './public/didntfindshit.jpg' 7 | end 8 | 9 | def max_age 10 | 30 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /sources/image_iterator.rb: -------------------------------------------------------------------------------- 1 | module ImageIterator 2 | def enum 3 | @enum ||= @images.to_enum 4 | end 5 | 6 | def next 7 | begin 8 | image = Kernel.open(enum.next) 9 | end while not image.content_type =~ /image/ 10 | image 11 | end 12 | 13 | def rewind 14 | enum.rewind 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :gemcutter 2 | 3 | gem 'sequel' 4 | gem 'rest-client' 5 | gem 'newrelic_rpm' 6 | gem 'aws-s3', :require => 'aws/s3' 7 | gem 'json' 8 | gem 'rmagick' 9 | gem 'sinatra' 10 | gem 'sinatra-cache-assets' 11 | gem 'rake' 12 | gem 'racksh' 13 | gem 'rack-cache', require: false 14 | gem 'dalli' 15 | gem 'unicorn' 16 | 17 | group :development,:test do 18 | gem 'rspec' 19 | gem 'pry' 20 | gem 'rack-test', require: 'rack/test' 21 | gem 'ruby-prof', require: false 22 | end 23 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # Bundler.setup :production 2 | require './fyn' 3 | require 'sinatra-cache-assets' 4 | require 'rack/cache' 5 | require 'newrelic_rpm' 6 | 7 | NewRelic::Agent.after_fork(:force_reconnect => true) if defined? Unicorn 8 | 9 | if ENV['MEMCACHE_SERVERS'] 10 | use Rack::Cache, 11 | verbose: true, 12 | metastore: "memcached://#{ENV['MEMCACHE_SERVERS']}", 13 | entitystore: "memcached://#{ENV['MEMCACHE_SERVERS']}" 14 | end 15 | use Sinatra::CacheAssets 16 | run FuckYeahNouns::Application 17 | -------------------------------------------------------------------------------- /config/newrelic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | production: 3 | error_collector: 4 | capture_source: true 5 | enabled: true 6 | ignore_errors: ActionController::RoutingError 7 | apdex_t: 0.5 8 | ssl: false 9 | monitor_mode: true 10 | license_key: <%= ENV["NEW_RELIC_LICENSE_KEY"] %> 11 | developer_mode: false 12 | app_name: <%= ENV["NEW_RELIC_APP_NAME"] %> 13 | transaction_tracer: 14 | record_sql: obfuscated 15 | enabled: true 16 | stack_trace_threshold: 0.5 17 | transaction_threshold: apdex_f 18 | capture_params: false 19 | log_level: info -------------------------------------------------------------------------------- /actions/noun/black_listed.rb: -------------------------------------------------------------------------------- 1 | module Actions 2 | class Noun 3 | module BlackListed 4 | BLACKLIST = ["selinaferguson", "pwaring",'eddsowden','shakarshy','nickbrom', 'julietuesley','andrewbrin','dtox','abigailwessel', 'abby', 'angelaparriott', 'elizabethparriott'] 5 | def file 6 | Struct.new(:file, :max_age).new('./public/copyrightcomplaint.jpg',36000) 7 | end 8 | 9 | def self.blacklisted?(noun) 10 | noun = noun.dup 11 | noun.downcase! 12 | noun.gsub!(/[^\w]*/,'') 13 | BLACKLIST.include?(noun) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /actions/noun/nsfw.rb: -------------------------------------------------------------------------------- 1 | module Actions 2 | class Noun 3 | module NSFW 4 | WORK_INAPPROPRIATE = /boob|tit|cock|penis|vagina|pussy|dick|ass|fuck|shit|piss|sex|gay|lesbian|chick/ 5 | 6 | def self.enabled? 7 | @enabled 8 | end 9 | 10 | def self.enable! 11 | @enabled = true 12 | end 13 | 14 | def self.disable! 15 | @enabled = false 16 | end 17 | 18 | def nsfw? 19 | true 20 | end 21 | 22 | def image 23 | Struct.new(:file, :max_age).new('./public/didntfindshit.jpg',300) 24 | end 25 | 26 | def self.nsfw?(noun) 27 | noun =~ WORK_INAPPROPRIATE if enabled? 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/sources/spice_spec.rb: -------------------------------------------------------------------------------- 1 | require './sources/spice.rb' 2 | 3 | describe 'Spice' do 4 | describe '.up' do 5 | subject do 6 | Spice.up('apple') 7 | end 8 | 9 | it { should_not be_nil } 10 | end 11 | 12 | describe '.replace' do 13 | subject do 14 | Spice.replace('apple',['orange']) 15 | end 16 | 17 | it { should == 'orange' } 18 | end 19 | 20 | describe '.prefix' do 21 | subject do 22 | Spice.prefix('apple',['orange']) 23 | end 24 | 25 | it { should == 'orange apple' } 26 | end 27 | 28 | describe '.suffix' do 29 | subject do 30 | Spice.suffix('apple',['orange']) 31 | end 32 | 33 | it { should == 'apple orange' } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /actions/noun.rb: -------------------------------------------------------------------------------- 1 | require './actions/noun/nsfw' 2 | require './actions/noun/black_listed' 3 | 4 | module Actions 5 | class Noun 6 | attr_reader :noun, :nsfw 7 | 8 | def initialize(noun) 9 | @noun = noun 10 | end 11 | 12 | def shirt 13 | @shirt ||= Shirt.create(noun) 14 | end 15 | 16 | def image 17 | @image ||= Image.create(noun) 18 | end 19 | 20 | def self.random 21 | Spice.all.shuffle.first 22 | end 23 | 24 | def self.create(noun) 25 | instance = new(noun) 26 | 27 | instance.extend(BlackListed) if BlackListed.blacklisted?(noun) 28 | instance.extend(NSFW) if NSFW.nsfw?(noun) 29 | 30 | instance 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/sources/bing_spec.rb: -------------------------------------------------------------------------------- 1 | require './sources/bing.rb' 2 | 3 | describe 'Bing' do 4 | 5 | describe '.process_json' do 6 | subject { Bing.process_json(Bing.open_json('./spec/support/bing.json')) } 7 | 8 | its([:total]) { should equal 5660 } 9 | its([:images]) { should respond_to :each } 10 | end 11 | 12 | describe '.fetch' do 13 | before do 14 | Bing.stub(:open_json) do 15 | { 16 | "SearchResponse" => { 17 | "Image" => { 18 | "Total" => 1, 19 | "Results" => [{ 'MediaUrl' => 'http://example.com/example.jpg'}] 20 | } 21 | } 22 | } 23 | end 24 | 25 | Kernel.stub(:open) do 26 | open('./spec/support/test_image.jpg') 27 | end 28 | end 29 | 30 | let(:subject) { Bing.fetch('xbox') } 31 | its(:images) { should respond_to(:each) } 32 | its(:images) { should == ['http://example.com/example.jpg'] } 33 | end 34 | 35 | describe 'search_url' do 36 | subject { Bing.search_url('xbox') } 37 | it { should match(/http:\/\//) } 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /fyn.rb: -------------------------------------------------------------------------------- 1 | require './boot' 2 | ENV['APP_ROOT'] ||= File.dirname(__FILE__) 3 | 4 | require './actions/shirt' 5 | require './actions/image' 6 | require './actions/noun' 7 | require './sources/bing' 8 | 9 | module FuckYeahNouns 10 | class Application < Sinatra::Base 11 | configure :production do 12 | require 'newrelic_rpm' 13 | end 14 | 15 | set :public_folder, File.dirname(__FILE__) + '/public' 16 | 17 | before do 18 | cache_control :public, :must_revalidate, max_age: 36000 19 | end 20 | 21 | get '/' do 22 | erb :home 23 | end 24 | 25 | def noun(noun = params[:noun]) 26 | @noun ||= Actions::Noun.create(noun.freeze) 27 | end 28 | 29 | get '/shirt/:noun' do 30 | redirect noun.shirt.url 31 | end 32 | 33 | get '/random' do 34 | cache_control :private 35 | redirect '/' + Actions::Noun.random 36 | end 37 | 38 | get '/images/:noun.:format?' do 39 | send_file noun.image.file, type: :jpg, disposition: :inline 40 | end 41 | 42 | get '/:noun' do 43 | noun 44 | erb :noun 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/sources/image_iterator_spec.rb: -------------------------------------------------------------------------------- 1 | require './sources/image_iterator' 2 | 3 | class Dummy 4 | include ImageIterator 5 | attr_accessor :images 6 | 7 | def initialize(images=[]) 8 | @images = images 9 | end 10 | end 11 | 12 | describe 'ImageIterator' do 13 | before do 14 | Kernel.stub(:open) { |arg| arg } 15 | end 16 | 17 | let(:a) { stub(content_type: 'image') } 18 | let(:b) { stub(content_type: 'image') } 19 | let(:c) { stub(content_type: 'image') } 20 | 21 | context 'all valid images' do 22 | 23 | subject { Dummy.new([a,b,c]) } 24 | 25 | it 'iterates as expected' do 26 | subject.next.should == a 27 | subject.next.should == b 28 | subject.next.should == c 29 | expect { 30 | subject.next 31 | }.to raise_error StopIteration 32 | end 33 | end 34 | 35 | context 'one in-valid image' do 36 | 37 | let(:c) { stub(content_type: 'text') } 38 | 39 | subject { Dummy.new([a,b,c]) } 40 | 41 | it 'iterates as expected' do 42 | subject.next.should == a 43 | subject.next.should == b 44 | expect { 45 | subject.next 46 | }.to raise_error StopIteration 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /sources/bing.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'open-uri' 3 | require './sources/image_iterator' 4 | require './sources/spice' 5 | 6 | class Bing 7 | attr_reader :noun, :images, :result, :json 8 | include ImageIterator 9 | 10 | def initialize(noun) 11 | @noun = noun 12 | 13 | @images = [] 14 | @result = nil 15 | @json = nil 16 | end 17 | 18 | def self.fetch(noun) 19 | instance = new(noun) 20 | instance.search! 21 | instance 22 | end 23 | 24 | def search! 25 | @url = Bing.search_url(@noun) 26 | @result = Bing.open_json(@url) 27 | @json = Bing.process_json(@result) 28 | 29 | @images = @json[:images] 30 | 31 | nil 32 | end 33 | 34 | def self.process_json(json) 35 | result = json["SearchResponse"] 36 | images = result["Image"] 37 | 38 | { 39 | total: images['Total'], 40 | images: images['Results'].map { |image| image['MediaUrl'] } 41 | } 42 | end 43 | 44 | def self.search_url(noun) 45 | query = CGI.escape(Spice.up(noun)) 46 | ENV['bing'] = '0DA7AB4A8F9C686A25B23945975F9CCF4E8D3592' 47 | filter = if (rand(10)+1)%2 == 0 48 | "&Image.Filters=Face:Portrait" 49 | else 50 | "" 51 | end 52 | "http://api.bing.net/json.aspx?AppId=#{ENV['bing']}&Query=#{query}&Sources=Image&Version=2.0&Market=en-us&Adult=On&Image.Count=10&Image.Offset=0#{filter}" 53 | end 54 | 55 | def self.open_json(url) 56 | Kernel.open(url) do |fh| 57 | return JSON.parse(fh.read) 58 | end 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /spec/actions/image_spec.rb: -------------------------------------------------------------------------------- 1 | require './actions/image' 2 | 3 | describe Actions::Image do 4 | 5 | describe '.create' do 6 | def image(name) 7 | Actions::Image.create(name) 8 | end 9 | 10 | def test_image 11 | File.new('spec/support/test_image.jpg') 12 | end 13 | 14 | def test_iterator 15 | result = [test_image] 16 | def result.next 17 | self.first 18 | end 19 | result 20 | end 21 | 22 | context 'everything works' do 23 | before(:each) do 24 | Actions::Image.stub(:annotate) { test_image } 25 | Actions::Image.stub(:fetch) { test_iterator } 26 | end 27 | 28 | let(:subject) { image("sleepy") } 29 | 30 | its('file.path') { should_not match(/didntfindshit/) } 31 | end 32 | 33 | context 'fetch failed' do 34 | before(:each) do 35 | Actions::Image.stub(:annotate) { test_image } 36 | Actions::Image.stub(:fetch) { raise 'fetch failed' } 37 | end 38 | 39 | let(:subject) { image('sleepy') } 40 | its('file.path') { should match(/didntfindshit/) } 41 | 42 | end 43 | 44 | context 'annotation success' do 45 | before(:each) do 46 | Actions::Image.stub(:fetch) { test_iterator } 47 | end 48 | 49 | it 'succeeds ' do 50 | expect { 51 | image('sleepy') 52 | }.to_not raise_error 53 | end 54 | 55 | it 'has a file' do 56 | image('sleepy').file.path.should_not be_empty 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/actions/noun_spec.rb: -------------------------------------------------------------------------------- 1 | require './boot' 2 | 3 | require './actions/noun' 4 | 5 | describe Actions::Noun do 6 | context "valid noun" do 7 | subject do 8 | Actions::Noun.create("sleepy") 9 | end 10 | 11 | it('runs') { should_not be_nil } 12 | xit(:shirt) { should be_valid } 13 | 14 | xit('image.file') { should_not match(/didntfindshit/) } 15 | 16 | end 17 | 18 | context "nsfw noun" do 19 | let(:subject) do 20 | Actions::Noun.create("boob") 21 | end 22 | 23 | it "runs" do 24 | should_not be_nil 25 | end 26 | xit ('shirt.url') { should match('')} 27 | xit ('image.file') { should match(/didntfindshit/) } 28 | end 29 | end 30 | 31 | describe Actions::Noun::BlackListed do 32 | describe '.blacklisted?' do 33 | let(:subject) { Actions::Noun::BlackListed } 34 | 35 | context 'blacklisted' do 36 | it 'is true' do 37 | subject.blacklisted?('selinaferguson').should be_true 38 | end 39 | end 40 | 41 | context 'not blacklisted' do 42 | it 'is false' do 43 | subject.blacklisted?('sleepy').should be_false 44 | end 45 | end 46 | end 47 | end 48 | 49 | describe Actions::Noun::NSFW do 50 | describe '.nsfw?' do 51 | before(:all) { Actions::Noun::NSFW.enable! } 52 | after(:all) { Actions::Noun::NSFW.disable! } 53 | let(:subject) { Actions::Noun::NSFW } 54 | context 'is nsfw' do 55 | it 'is true' do 56 | subject.nsfw?('boob').should be_true 57 | end 58 | end 59 | 60 | context 'is not nsfw' do 61 | it 'is false' do 62 | subject.nsfw?('sleepy').should be_false 63 | end 64 | end 65 | end 66 | end 67 | 68 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | aws-s3 (0.6.2) 5 | builder 6 | mime-types 7 | xml-simple 8 | builder (3.0.0) 9 | coderay (1.0.5) 10 | dalli (1.1.5) 11 | diff-lcs (1.1.3) 12 | json (1.6.5) 13 | kgio (2.7.2) 14 | method_source (0.7.0) 15 | mime-types (1.17.2) 16 | newrelic_rpm (3.3.2) 17 | pry (0.9.8.2) 18 | coderay (~> 1.0.5) 19 | method_source (~> 0.7) 20 | slop (>= 2.4.4, < 3) 21 | rack (1.4.1) 22 | rack-cache (1.1) 23 | rack (>= 0.4) 24 | rack-protection (1.2.0) 25 | rack 26 | rack-test (0.6.1) 27 | rack (>= 1.0) 28 | racksh (0.9.10) 29 | rack (>= 1.0) 30 | rack-test (>= 0.5) 31 | raindrops (0.8.0) 32 | rake (0.9.2.2) 33 | rest-client (1.6.7) 34 | mime-types (>= 1.16) 35 | rmagick (2.13.1) 36 | rspec (2.8.0) 37 | rspec-core (~> 2.8.0) 38 | rspec-expectations (~> 2.8.0) 39 | rspec-mocks (~> 2.8.0) 40 | rspec-core (2.8.0) 41 | rspec-expectations (2.8.0) 42 | diff-lcs (~> 1.1.2) 43 | rspec-mocks (2.8.0) 44 | ruby-prof (0.10.8) 45 | sequel (3.32.0) 46 | sinatra (1.3.2) 47 | rack (~> 1.3, >= 1.3.6) 48 | rack-protection (~> 1.2) 49 | tilt (~> 1.3, >= 1.3.3) 50 | sinatra-cache-assets (0.0.3) 51 | sinatra (>= 1.0) 52 | slop (2.4.4) 53 | tilt (1.3.3) 54 | unicorn (4.2.0) 55 | kgio (~> 2.6) 56 | rack 57 | raindrops (~> 0.7) 58 | xml-simple (1.1.1) 59 | 60 | PLATFORMS 61 | ruby 62 | 63 | DEPENDENCIES 64 | aws-s3 65 | dalli 66 | json 67 | newrelic_rpm 68 | pry 69 | rack-cache 70 | rack-test 71 | racksh 72 | rake 73 | rest-client 74 | rmagick 75 | rspec 76 | ruby-prof 77 | sequel 78 | sinatra 79 | sinatra-cache-assets 80 | unicorn 81 | -------------------------------------------------------------------------------- /actions/shirt.rb: -------------------------------------------------------------------------------- 1 | module Actions 2 | class Shirt 3 | def self.create(noun) 4 | url = "http://open-api.cafepress.com/authentication.getUserToken.cp?v=3&appKey=#{ENV['CAFEPRESS_KEY']}&email=#{ENV['CAFEPRESS_EMAIL']}&password=#{ENV['CAFEPRESS_PASSWORD']}" 5 | key = open(url).read.scan(/(.*)<\/value>/).flatten.first 6 | 7 | data = FuckYeahNouns.fuck_noun(noun, true) 8 | 9 | tmppath="tmp/#{rand 10000000}" 10 | File.open(tmppath, 'wb') { |f| f.write data } 11 | 12 | hash = { 13 | :cpFile1 => File.new(tmppath), 14 | # :cpFile2 => nil, 15 | :appKey => ENV['CAFEPRESS_KEY'], 16 | :userToken => key, 17 | :folder => "Images" 18 | } 19 | 20 | action = "http://upload.cafepress.com/image.upload.cp" 21 | x = RestClient.post action, hash.merge(:multipart => true) 22 | 23 | File.unlink(tmppath) 24 | 25 | imgref = x.scan(/(.*)<\/value>/).flatten.first 26 | 27 | # "http://open-api.cafepress.com/merchandise.list.cp?v=3&appKey=#{ENV['CAFEPRESS_KEY']}" 28 | # merch_id=2 29 | # url = "http://open-api.cafepress.com/product.create.cp?v=3&appKey=#{ENV['CAFEPRESS_KEY']}&merchandiseId=#{merch_id}&fieldTypes=optional" 30 | 31 | xml = <<-XML 32 | 33 | 34 | 35 | 36 | XML 37 | xml.sub!(/^\s*/,'') 38 | 39 | url = "http://open-api.cafepress.com/product.save.cp?v=3&appKey=#{ENV['CAFEPRESS_KEY']}&userToken=#{key}&value=#{CGI.escape xml}" 40 | 41 | z = RestClient.get(url) 42 | 43 | pid = z.scan(/ e 32 | count += 1 33 | count < 5 ? retry : raise(e) 34 | end 35 | end 36 | 37 | def self.create(noun=nil) 38 | instance = new(noun) 39 | 40 | noun or return instance.extend(Didntfindshit) 41 | 42 | try_5_times! do 43 | instance.fetch! 44 | instance.annotate! 45 | end rescue instance.extend(Didntfindshit) 46 | 47 | instance 48 | end 49 | 50 | def self.fetch(noun,source=Bing) 51 | source.fetch(noun) 52 | end 53 | 54 | def self.annotate(img, noun, shirtastic=false) 55 | picture = Magick::Image.from_blob(img.read).first 56 | width,height = picture.columns, picture.rows 57 | 58 | if shirtastic 59 | factor = 2000/600.0 60 | if width > height 61 | picture.resize!(2000,2000*(height/width.to_f)) 62 | else 63 | picture.resize!(2000*(width/height.to_f), 2000) 64 | end 65 | else 66 | factor = 1 67 | picture.resize!(600,600*(height/width.to_f)) 68 | end 69 | width,height = picture.columns, picture.rows 70 | 71 | overlay = Magick::Image.new(width, 100 * factor) 72 | picture.composite!(overlay, Magick::SouthGravity, Magick::MultiplyCompositeOp) 73 | 74 | caption = Magick::Draw.new 75 | caption.fill('white') 76 | caption.stroke('black') 77 | caption.font_stretch = Magick::ExtraCondensedStretch 78 | caption.font('Helvetica-Bold') 79 | caption.stroke_width(2 * factor) 80 | caption.pointsize(48 * factor) 81 | caption.font_weight(800) 82 | caption.text_align(Magick::CenterAlign) 83 | 84 | caption.text(width/2.0, height-(50*factor), "FUCK YEAH\n#{noun.upcase}") 85 | caption.draw(picture) 86 | 87 | picture.to_blob { self.quality = 50 } 88 | 89 | tmp = Tempfile.new('asdf') 90 | tmp.write(picture.to_blob) 91 | tmp 92 | end 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /views/home.rhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FUCK YEAH NOUNS 4 | 59 | 60 | 61 | 62 | Random 63 | 64 |
65 | 66 |
67 |
68 | 69 | 72 | 73 | 74 |
75 |

76 | © 2011 77 | Burke fucking Libbey Stefan fucking Penner, Tim fucking Herd. 78 |
79 | Another fine fucking SkullSpace project. 80 |
81 | Images provided by Bing! 82 |

83 | 84 | 85 | 93 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /views/noun.rhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FUCK YEAH <%= params[:noun].upcase.gsub(/[^\w\d\!\@\#\$\%\^\&\*\_\-\ ]/,'') %> 4 | 74 | 75 | 76 | 77 | Random 78 | 79 |
80 | 81 |
82 |
83 |
84 | 85 |
86 |
87 | 88 | 91 | 92 | 93 |
94 |
95 |

96 | © 2011 97 | Burke fucking Libbey Stefan fucking Penner, Tim fucking Herd. 98 |
99 | Another fine fucking SkullSpace project. 100 |
101 | Images provided by Bing! 102 |

103 | 104 | 112 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /spec/support/bing.json: -------------------------------------------------------------------------------- 1 | {"SearchResponse":{"Version":"2.0","Query":{"SearchTerms":"xbox site:microsoft.com"},"Image":{"Total":5660,"Offset":0,"Results":[{"Title":"Microsoft Unveils Xbox 360 Elite: Premium black console includes 120GB ...","MediaUrl":"http:\/\/www.microsoft.com\/presspass\/images\/press\/2007\/03-27xboxElite_lg.jpg","Url":"http:\/\/www.microsoft.com\/presspass\/press\/2007\/mar07\/03-27xbox360elitepr.mspx","DisplayUrl":"http:\/\/www.microsoft.com\/presspass\/press\/2007\/mar07\/03-27xbox360elitepr.mspx","Width":1500,"Height":2664,"FileSize":957345,"Thumbnail":{"Url":"http:\/\/ts1.mm.bing.net\/images\/thumbnail.aspx?q=1523534006404&id=8d28a05cecc734846fd62514b47b934d","ContentType":"image\/jpeg","Width":90,"Height":160,"FileSize":2205}},{"Title":"Microsoft Unveils Xbox 360 Elite: Premium black console includes 120GB ...","MediaUrl":"http:\/\/www.microsoft.com\/presspass\/images\/press\/2007\/03-27xboxElite.jpg","Url":"http:\/\/www.microsoft.com\/Presspass\/press\/2007\/mar07\/03-27Xbox360ElitePR.mspx","DisplayUrl":"http:\/\/www.microsoft.com\/Presspass\/press\/2007\/mar07\/03-27Xbox360ElitePR.mspx","Width":200,"Height":266,"FileSize":8314,"Thumbnail":{"Url":"http:\/\/ts1.mm.bing.net\/images\/thumbnail.aspx?q=1579492641508&id=28d300e27af45dabd1fb4417a34cbd66","ContentType":"image\/jpeg","Width":120,"Height":160,"FileSize":3046}},{"Title":"Microsoft Wave - Xbox 360 ","MediaUrl":"http:\/\/www.microsoft.com\/uk\/wave\/images\/articles\/New-Xbox.png","Url":"http:\/\/www.microsoft.com\/uk\/wave\/hardware-xbox.aspx","DisplayUrl":"http:\/\/www.microsoft.com\/uk\/wave\/hardware-xbox.aspx","Width":464,"Height":451,"FileSize":103587,"Thumbnail":{"Url":"http:\/\/ts4.mm.bing.net\/images\/thumbnail.aspx?q=1515788839131&id=97d943938c73b5e74ccd0a2440744d69","ContentType":"image\/jpeg","Width":160,"Height":155,"FileSize":3044}},{"Title":"Xbox Live Marketplace lets you download movies directly to your Xbox ...","MediaUrl":"http:\/\/www.microsoft.com\/global\/athome\/PublishingImages\/music\/xbox-live.gif","Url":"http:\/\/www.microsoft.com\/athome\/music\/movies.aspx","DisplayUrl":"http:\/\/www.microsoft.com\/athome\/music\/movies.aspx","Width":375,"Height":402,"FileSize":79864,"Thumbnail":{"Url":"http:\/\/ts1.mm.bing.net\/images\/thumbnail.aspx?q=1534148155120&id=5b40bf4ce25acda6ee762f9e4702e80a","ContentType":"image\/jpeg","Width":149,"Height":160,"FileSize":6074}},{"Title":"Description of the Xbox 360 power supply ","MediaUrl":"http:\/\/support.microsoft.com\/library\/images\/support\/kbgraphics\/PUBLIC\/EN-US\/XBOX\/HW\/PowerSupply\/connector.jpg","Url":"http:\/\/support.microsoft.com\/kb\/907635","DisplayUrl":"http:\/\/support.microsoft.com\/kb\/907635","Width":628,"Height":180,"FileSize":14575,"Thumbnail":{"Url":"http:\/\/ts3.mm.bing.net\/images\/thumbnail.aspx?q=1577122345746&id=28cce2711ab9c41232623c633debdd50","ContentType":"image\/jpeg","Width":160,"Height":45,"FileSize":2089}},{"Title":"Xbox LIVE Screens","MediaUrl":"http:\/\/advertising.microsoft.com\/canada\/en\/WWImages\/en-ca\/ForAdvertisers\/Xbox_LIVE_1-546w.jpg","Url":"http:\/\/advertising.microsoft.com\/canada\/en\/ForAdvertisers\/default.aspx?pageid=2606","DisplayUrl":"http:\/\/advertising.microsoft.com\/canada\/en\/ForAdvertisers\/default.aspx?pageid=2606","Width":546,"Height":307,"FileSize":115787,"Thumbnail":{"Url":"http:\/\/ts4.mm.bing.net\/images\/thumbnail.aspx?q=1519895586187&id=640ffd336ce94f8f0148bbf9a0efbdc1","ContentType":"image\/jpeg","Width":160,"Height":89,"FileSize":2899}},{"Title":" ... that you've simplified your setup, check your Xbox LIVE performance","MediaUrl":"http:\/\/support.microsoft.com\/library\/images\/support\/kbgraphics\/PUBLIC\/EN-US\/XBOX\/LIVE\/connect\/xbmoddisconnect.jpg","Url":"http:\/\/support.microsoft.com\/kb\/909817","DisplayUrl":"http:\/\/support.microsoft.com\/kb\/909817","Width":360,"Height":275,"FileSize":25209,"Thumbnail":{"Url":"http:\/\/ts3.mm.bing.net\/images\/thumbnail.aspx?q=1519059934902&id=ddbac23829f94065d2ce8390ced7b8c7","ContentType":"image\/jpeg","Width":160,"Height":122,"FileSize":2714}},{"Title":"How to set up and use your Xbox 360 Wireless Racing Wheel ","MediaUrl":"http:\/\/support.microsoft.com\/library\/images\/support\/kbgraphics\/Public\/EN-US\/927347L.jpg","Url":"http:\/\/support.microsoft.com\/kb\/927347\/hi","DisplayUrl":"http:\/\/support.microsoft.com\/kb\/927347\/hi","Width":450,"Height":438,"FileSize":39024,"Thumbnail":{"Url":"http:\/\/ts4.mm.bing.net\/images\/thumbnail.aspx?q=1518819087919&id=2626aaac29d6cf5f273be3b3619d4346","ContentType":"image\/jpeg","Width":160,"Height":155,"FileSize":4873}},{"Title":"Microsoft RetailWeb - Xbox 360 250 GB HaloReach-FableIII","MediaUrl":"http:\/\/www.microsoft.com\/germany\/partner\/retail\/data\/Xbox_360\/Hardware\/Xbox_360_Konsole\/Xbox_360_250_GB_HaloReach-FableIII\/Produktabbildungen\/Xbox360_Gloss_Contoller_7-8View.jpg","Url":"http:\/\/www.microsoft.com\/germany\/partner\/retail\/content.aspx?content=Xbox_360\/Hardware\/Xbox_360_Konsole\/Xbox_360_250_GB_HaloReach-FableIII","DisplayUrl":"http:\/\/www.microsoft.com\/germany\/partner\/retail\/content.aspx?content=Xbox_360\/Hardware\/Xbox_360_Konsole\/Xbox_360_250_GB_HaloReach-FableIII","Width":1584,"Height":1440,"FileSize":671701,"Thumbnail":{"Url":"http:\/\/ts1.mm.bing.net\/images\/thumbnail.aspx?q=1451508570920&id=d6a2494dff1abd7e21eb682e559e9144","ContentType":"image\/jpeg","Width":160,"Height":145,"FileSize":2703}},{"Title":"Kinect for Xbox 360’ is Official Name of Microsoft’s Controller ...","MediaUrl":"http:\/\/www.microsoft.com\/presspass\/presskits\/xbox\/images\/Kinect_logo_print.jpg","Url":"http:\/\/www.microsoft.com\/presspass\/features\/2010\/jun10\/06-13kinectintroduced.mspx","DisplayUrl":"http:\/\/www.microsoft.com\/presspass\/features\/2010\/jun10\/06-13kinectintroduced.mspx","Width":2400,"Height":1010,"FileSize":328217,"Thumbnail":{"Url":"http:\/\/ts4.mm.bing.net\/images\/thumbnail.aspx?q=1519042242411&id=08129f990cb77e7e25b4f1f5a87ba772","ContentType":"image\/jpeg","Width":160,"Height":67,"FileSize":3204}}]}}} -------------------------------------------------------------------------------- /sources/spice.rb: -------------------------------------------------------------------------------- 1 | module Spice 2 | 3 | extend self 4 | 5 | def all 6 | RANDOM + MEMES + CELEBS 7 | end 8 | 9 | def up(noun) 10 | return noun 11 | #will have alot more fun here.. 12 | 13 | a = noun.hash % 10 14 | b = noun.length % 10 15 | 16 | seed = case b 17 | when 0..4 then RANDOM 18 | when 5..7 then [] 19 | else 20 | CELEBS 21 | end 22 | 23 | result = case a 24 | when 1 then prefix(noun,seed) 25 | when 2..7 then noun 26 | else 27 | suffix(noun,seed) 28 | end 29 | 30 | result 31 | end 32 | 33 | def random(collection) 34 | collection.shuffle.first || '' 35 | end 36 | 37 | def replace(noun,collection) 38 | random(collection) 39 | end 40 | 41 | def prefix(noun,collection) 42 | random(collection) + " " + noun 43 | end 44 | 45 | def suffix(noun,collection) 46 | noun + " " + random(collection) 47 | end 48 | 49 | RANDOM = [ 50 | 'boobs', 51 | 'sexy', 52 | 'wicked', 53 | 'hot', 54 | 'hotest', 55 | 'sex', 56 | '18', 57 | '21', 58 | 'cute' 59 | ] 60 | 61 | MEMES = [ 62 | "Blendtec", 63 | "Cooks Source infringement controversy", 64 | "FreeCreditReport.com", 65 | "Embrace Life", 66 | "HeadOn", 67 | "Lowermybills.com", 68 | "Shake Weight", 69 | "The Man Your Man Could Smell Like", 70 | "Young Darth Vader", 71 | "JibJab", 72 | "Caramelldansen", 73 | "Charlie the Unicorn", 74 | "Dancing baby", 75 | "Happy Tree Friends", 76 | "Homestar Runner", 77 | "Joe Cartoon", 78 | "Loituma Girl", 79 | "Leekspin", 80 | "My Little Pony", 81 | "Friendship Is Magic", 82 | "Salad Fingers", 83 | "This Land is Your Land", 84 | "Ultimate Showdown of Ultimate Destiny", 85 | "Weebl and Bob", 86 | "Goodtimes virus", 87 | "The Blair Witch Project", 88 | "Brokeback Mountain", 89 | "Cloverfield", 90 | "Mega Shark Versus Giant Octopus", 91 | "Party Gir", 92 | "Snakes on a Plane", 93 | "All Your Base", 94 | "All your base are belong to us", 95 | "Giant Enemy Crab", 96 | "Leeroy Jenkins", 97 | "Line Rider", 98 | "I Love Bees", 99 | "Tron Guy", 100 | "Ate my balls", 101 | "Allison Stokke", 102 | "Baidu 10 Mythical Creatures", 103 | "Bert is Evil", 104 | "Cigar guy", 105 | "Crasher Squirrel", 106 | "Goatse.cx", 107 | "Heineken Looter Guy", 108 | "A LOLcat", 109 | "Islamic Rage Boy", 110 | "Kermit Bale", 111 | "Little Fatty", 112 | "LOLcat", 113 | "O RLY?", 114 | "Oolong", 115 | "The Saugeen Stripper", 116 | "Seriously McDonalds", 117 | "Tron Guy", 118 | "Vancouver Riot Kiss", 119 | "Rosines Chavez", 120 | "Gary Brolsma", 121 | "The Numa Numa Guy", 122 | "Average Homeboy", 123 | "Dancing Banana", 124 | "Canon Rock", 125 | "Chocolate Rain", 126 | "Dear Sister", 127 | "Ekrem Jevric", 128 | "Friday", 129 | "Hampster Dance", 130 | "Hurra Torpedo", 131 | "JK Wedding Entrance Dance", 132 | "Literal music video", 133 | "Little Superstar", 134 | "Lucian Piane, aka RevoLucian", 135 | "The Muppets: Bohemian Rhapsody", 136 | "McDonald's rap", 137 | "Numa Numa", 138 | "OK Go music videos", 139 | "Pants on the Ground", 140 | "Red Solo Cup", 141 | "Take U to da Movies", 142 | "Techno Viking", 143 | "Prison Thriller", 144 | "Trololo", 145 | "Twelve Days of Christmas", 146 | "United Breaks Guitars", 147 | "We Gon Rock", 148 | "Freecycling", 149 | "One red paperclip", 150 | "Secret London", 151 | "Three Wolf Moon", 152 | "2 Girls 1 Cup", 153 | "The Annoying Orange", 154 | "Ask a Ninja", 155 | "Badger Badger Badger", 156 | "Benny Lava", 157 | "Bed Intruder Song", 158 | "Boom goes the dynamite", 159 | "Charlie Bit My Finger", 160 | "Charlie Chaplin Time Travel Video", 161 | "The Crazy Nastyass Honey Badger", 162 | "Dancing Matt", 163 | "Diet Coke and Mentos", 164 | "Double Rainbow", 165 | "Don't Tase Me, Bro!", 166 | "Downfall Parodies", 167 | "Dramatic Chipmunk", 168 | "Edgar's fall", 169 | "eHarmony Video Bio", 170 | "Epic Beard Man", 171 | "Fenton", 172 | "Fred Figglehorn", 173 | "Heroine of Hackney", 174 | "I Like Turtles", 175 | "Impossible Is Nothing", 176 | "Jag har mensvark", 177 | "Ken Lee", 178 | "Kersal Massive", 179 | "Keyboard Cat", 180 | "The Last Lecture", 181 | "League of Ireland fan", 182 | "Leave Britney Alone!", 183 | "Lonelygirl15", 184 | "Maru the cat", 185 | "Melissa Theuriau", 186 | "Nek Minnit", 187 | "Nyan Cat", 188 | "Obama Girl", 189 | "Rickrolling", 190 | "Shreds", 191 | "Star Wars Kid", 192 | "Tyson", 193 | "UFO Phil", 194 | "What What (In the Butt)", 195 | "Wii Fit Girl", 196 | "Winnebago Man", 197 | "Youtube Poop", 198 | "Zangief Kid", 199 | "Other", 200 | "Creepypasta", 201 | "Figwit", 202 | "Illegal flower tribute", 203 | "Vuvuzelas", 204 | "Zombie Jesus", 205 | ] 206 | 207 | CELEBS = [ 208 | 'Katy Perry', 209 | 'Tammin Sursok', 210 | 'Evan Rachel Wood', 211 | 'Laura Vandervoort', 212 | 'kim kardashian', 213 | 'Eva Mendes', 214 | 'Leighton Meester', 215 | 'Lauren Conrad', 216 | 'Adrianne Palicki', 217 | 'Julianne Hough', 218 | 'Emily Van Camp', 219 | 'Blake Lively', 220 | 'Kristen Kreuk', 221 | 'Kaley Cuoco', 222 | 'Katharine McPhee', 223 | 'Gemma Arteron', 224 | 'Keira Knightley', 225 | 'Michelle Trachtenberg', 226 | 'Ashlee Simpson-Wentz', 227 | 'Emma Roberts', 228 | 'Sarah Wright', 229 | 'Amanda Bynes', 230 | 'Colbie Calliat', 231 | 'Maria Sharapova', 232 | 'Elisha Cuthbert', 233 | 'Aimee Teegarden', 234 | 'Kendra Wilksinon', 235 | 'Karissa and Kristina Shannon', 236 | 'Adrienne Bailon', 237 | 'Lindsay Lohan', 238 | 'Maggie Grace', 239 | 'Lyndsy Fonseca', 240 | 'Emma Stone', 241 | 'Rihanna', 242 | 'Vanessa Hudgens', 243 | 'Ashley Tisdale', 244 | 'Hayden Panettiere', 245 | 'Audrina Patridge', 246 | 'Emma Watson', 247 | 'Megan Fox', 248 | 'Scarlett Johanssen', 249 | 'Mila Kunis', 250 | 'Jennifer Lopez' 251 | ] 252 | end 253 | --------------------------------------------------------------------------------