rturk 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.markdown +77 -0
- data/VERSION +1 -0
- data/examples/blank_slate.rb +6 -0
- data/examples/external_page.rb +33 -0
- data/examples/mturk.sample.yml +8 -0
- data/examples/newtweet.html +91 -0
- data/examples/review_answer.rb +8 -0
- data/lib/rturk.rb +11 -0
- data/lib/rturk/answer.rb +20 -0
- data/lib/rturk/custom_operations.rb +80 -0
- data/lib/rturk/external_question_builder.rb +22 -0
- data/lib/rturk/requester.rb +90 -0
- data/lib/rturk/utilities.rb +17 -0
- data/spec/answer_spec.rb +24 -0
- data/spec/external_question_spec.rb +27 -0
- data/spec/mturk.sample.yml +8 -0
- data/spec/requester_spec.rb +29 -0
- data/spec/spec_helper.rb +10 -0
- metadata +100 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Mark Percival
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# RTurk - A ridiculously simple Mechanical Turk library in Ruby
|
2
|
+
|
3
|
+
## What's it do?!?
|
4
|
+
|
5
|
+
RTurk is designed to fire off Mechanical Turk tasks for pages that reside on a external site.
|
6
|
+
|
7
|
+
The pages could be a part of a rails app, or just a simple javascript enabled form.
|
8
|
+
|
9
|
+
If you want to build forms that are hosted on Mechanical Turk, this is not the library you need.
|
10
|
+
You'd be better off with amazon's official library, in all its XML cruftiness.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
sudo gem install markpercival-rturk --source http://gems.github.com
|
15
|
+
|
16
|
+
## Use
|
17
|
+
|
18
|
+
Let's say you have a form at "http://myapp.com/turkers/add_tags" where Turkers can add some tags to items in your catalogue.
|
19
|
+
|
20
|
+
### Creating HIT's
|
21
|
+
|
22
|
+
require 'rturk'
|
23
|
+
|
24
|
+
props = {:Title=>"Add tags to an item",
|
25
|
+
:MaxAssignments=>1, :LifetimeInSeconds=>3600,
|
26
|
+
:Reward=>{:Amount=>0.05, :CurrencyCode=>"USD"},
|
27
|
+
:Keywords=>"twitter, blogging, writing, english",
|
28
|
+
:Description=>"Simply add some tags for me",
|
29
|
+
:RequesterAnnotation=>"Example1",
|
30
|
+
:AssignmentDurationInSeconds=>3600, :AutoApprovalDelayInSeconds=>3600,
|
31
|
+
:QualificationRequirement=>[{
|
32
|
+
# Approval rate of greater than 90%
|
33
|
+
:QualificationTypeId=>"000000000000000000L0",
|
34
|
+
:IntegerValue=>90,
|
35
|
+
:Comparator=>"GreaterThan",
|
36
|
+
:RequiredToPreview=>"false"
|
37
|
+
}]
|
38
|
+
}
|
39
|
+
|
40
|
+
@turk = RTurk::Requester.new(AWSAccessKeyId, AWSAccessKey, :sandbox => true)
|
41
|
+
page = RTurk::ExternalQuestionBuilder.build(
|
42
|
+
"http://myapp.com/turkers/add_tags", :item_id => '1234')
|
43
|
+
|
44
|
+
# Turkers will be directed to http://myapp.com/turkers/add_tags?item_id=1234&AssignmentId=abcd12345
|
45
|
+
|
46
|
+
p @turk.create_hit(props, page)
|
47
|
+
|
48
|
+
### Reviewing HIT's
|
49
|
+
|
50
|
+
require 'rturk'
|
51
|
+
@turk = RTurk::Requester.new(AWSAccessKeyId, AWSAccessKey, :sandbox => true)
|
52
|
+
|
53
|
+
p @turk.getAssignmentsForHIT(:HITId => 'abcde1234567890')
|
54
|
+
|
55
|
+
## Nitty Gritty
|
56
|
+
|
57
|
+
Here's a quick peak at what happens on the Mechanical Turk side.
|
58
|
+
|
59
|
+
A worker takes a look at your hit. The page will contain an iframe with your external URL loaded inside of it.
|
60
|
+
|
61
|
+
Amazon will append the AssignmentID parameter to the URL for your own information. In preview mode this will look like
|
62
|
+
http://myapp.com/turkers/add_tags?item_id=1234&AssignmentId=ASSIGNMENT_ID_NOT_AVAILABLE
|
63
|
+
|
64
|
+
If the Turker accepts the HIT, the page will reload and the iframe URL will resemble
|
65
|
+
|
66
|
+
http://myapp.com/turkers/add_tags?item_id=1234&AssignmentId=1234567890123456789ABC
|
67
|
+
|
68
|
+
The form in your page MUST CONTAIN the AssignmentID in a hidden input element. You could do this on the server side with a rails app, or on the client side with javascript(check the examples)
|
69
|
+
|
70
|
+
Anything submitted in this form will be sent to Amazon and saved for your review later.
|
71
|
+
|
72
|
+
## More information
|
73
|
+
|
74
|
+
Take a look at the [Amazon MTurk developer docs](http://docs.amazonwebservices.com/AWSMechTurk/latest/AWSMechanicalTurkRequester/) for more information. They have a complete list of API operations, all of which can be called with this library.
|
75
|
+
|
76
|
+
|
77
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.3
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require '../lib/rturk'
|
2
|
+
|
3
|
+
props = {:Title=>"Write a twitter update",
|
4
|
+
:MaxAssignments=>1, :LifetimeInSeconds=>3600,
|
5
|
+
:Reward=>{:Amount=>0.10, :CurrencyCode=>"USD"},
|
6
|
+
:Keywords=>"twitter, blogging, writing, english",
|
7
|
+
:Description=>"Simply write a twitter update for me",
|
8
|
+
:RequesterAnnotation=>"OptionalNote",
|
9
|
+
:AssignmentDurationInSeconds=>3600, :AutoApprovalDelayInSeconds=>3600,
|
10
|
+
:QualificationRequirement=>[{
|
11
|
+
:QualificationTypeId=>"000000000000000000L0",
|
12
|
+
:IntegerValue=>90,
|
13
|
+
:Comparator=>"GreaterThan",
|
14
|
+
:RequiredToPreview=>"false"
|
15
|
+
}]
|
16
|
+
}
|
17
|
+
root = File.expand_path(File.dirname(__FILE__))
|
18
|
+
aws = YAML.load(File.open(File.join(root, 'mturk.yml')))
|
19
|
+
@turk = RTurk::Requester.new(aws['AWSAccessKeyId'], aws['AWSAccessKey'], :sandbox => true)
|
20
|
+
page = RTurk::ExternalQuestionBuilder.build("http://s3.amazonaws.com/mpercival.com/newtweet.html", :id => 'foo')
|
21
|
+
|
22
|
+
puts "*" * 80
|
23
|
+
puts "This is the XML created for the external page question \n #{page}"
|
24
|
+
|
25
|
+
puts "*" * 80
|
26
|
+
hit = @turk.create_hit(props, page)
|
27
|
+
puts "And the response from CreateHIT operation"
|
28
|
+
p hit
|
29
|
+
|
30
|
+
puts "*" * 80
|
31
|
+
puts "Created a new HIT which can be found at #{@turk.url_for_hit_type(hit['HIT']['HITTypeId'])}"
|
32
|
+
|
33
|
+
File.open(File.join(root ,"last_hit"), "w") {|f| f.write(hit['HIT']['HITId']) }
|
@@ -0,0 +1,91 @@
|
|
1
|
+
<!-- This file needs to be hosted on an external server. -->
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
6
|
+
<title>Title, an XHTML requirement. This message brought to you by the W3C</title>
|
7
|
+
<script type='text/javascript'>
|
8
|
+
/*<![CDATA[*/
|
9
|
+
|
10
|
+
//
|
11
|
+
// This method Gets URL Parameters (GUP)
|
12
|
+
//
|
13
|
+
function gup( name )
|
14
|
+
{
|
15
|
+
var regexS = "[\\?&]"+name+"=([^&#]*)";
|
16
|
+
var regex = new RegExp( regexS );
|
17
|
+
var tmpURL = window.location.href;
|
18
|
+
var results = regex.exec( tmpURL );
|
19
|
+
if( results == null )
|
20
|
+
return "";
|
21
|
+
else
|
22
|
+
return results[1];
|
23
|
+
}
|
24
|
+
|
25
|
+
//
|
26
|
+
// This method decodes the query parameters that were URL-encoded
|
27
|
+
//
|
28
|
+
function decode(strToDecode)
|
29
|
+
{
|
30
|
+
var encoded = strToDecode;
|
31
|
+
return unescape(encoded.replace(/\+/g, " "));
|
32
|
+
}
|
33
|
+
|
34
|
+
function lengthCheck()
|
35
|
+
{
|
36
|
+
if (document.getElementById('tweet').value.length > 120) {
|
37
|
+
alert("Must be less than 140 characters! Currently " + document.getElementById('tweet').value.length);
|
38
|
+
return false;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
/*]]>*/
|
44
|
+
</script>
|
45
|
+
</head>
|
46
|
+
<body>
|
47
|
+
<form id="mturk_form" method="post" action="http://www.mturk.com/mturk/externalSubmit" onSubmit="return lengthCheck()">
|
48
|
+
<input type="hidden" id="assignmentId" name="assignmentId" value="" />
|
49
|
+
<h1>Help me write a clever twitter update</h1>
|
50
|
+
|
51
|
+
<p />
|
52
|
+
<h2>Criteria</h2>
|
53
|
+
<ul>
|
54
|
+
<li>You should be familiar with Twitter and microblogging</li>
|
55
|
+
<li>MUST BE LESS THAN 120 CHARACTERS</li>
|
56
|
+
<li>Could be about an interesting topic or current event</li>
|
57
|
+
<li>Could also make up something about what I'm currently doing</li>
|
58
|
+
<li>Should not be vulgar or crass</li>
|
59
|
+
|
60
|
+
<li>Should fool people into thinking I am clever and witty</li>
|
61
|
+
</ul>
|
62
|
+
<textarea rows="4" cols="40" tabindex="1" autocomplete="off" name="tweet" id="tweet"></textarea>
|
63
|
+
|
64
|
+
<p/>
|
65
|
+
<input id="submitButton" type="submit" name="Submit" value="Submit" />
|
66
|
+
<p/>
|
67
|
+
|
68
|
+
</form>
|
69
|
+
<script type='text/javascript'>
|
70
|
+
/*<![CDATA[*/
|
71
|
+
document.getElementById('assignmentId').value = gup('assignmentId');
|
72
|
+
|
73
|
+
|
74
|
+
//
|
75
|
+
// Check if the worker is PREVIEWING the HIT or if they've ACCEPTED the HIT
|
76
|
+
//
|
77
|
+
if (gup('assignmentId') == "ASSIGNMENT_ID_NOT_AVAILABLE")
|
78
|
+
{
|
79
|
+
// If we're previewing, disable the button and give it a helpful message
|
80
|
+
document.getElementById('submitButton').disabled = true;
|
81
|
+
document.getElementById('submitButton').value = "You must ACCEPT the HIT before you can submit the results.";
|
82
|
+
} else {
|
83
|
+
var form = document.getElementById('mturk_form');
|
84
|
+
if (document.referrer && ( document.referrer.indexOf('workersandbox') != -1) ) {
|
85
|
+
form.action = "http://workersandbox.mturk.com/mturk/externalSubmit";
|
86
|
+
}
|
87
|
+
}
|
88
|
+
/*]]>*/
|
89
|
+
</script>
|
90
|
+
</body>
|
91
|
+
</html>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require '../lib/rturk'
|
2
|
+
root = File.expand_path(File.dirname(__FILE__))
|
3
|
+
aws = YAML.load(File.open(File.join(root, 'mturk.yml')))
|
4
|
+
@turk = RTurk::Requester.new(aws['AWSAccessKeyId'], aws['AWSAccessKey'], :sandbox => true)
|
5
|
+
|
6
|
+
last_hit = File.open(File.join(root, 'last_hit'), 'r').read
|
7
|
+
|
8
|
+
p @turk.getAssignmentsForHIT(:HITId => last_hit)
|
data/lib/rturk.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module RTurk
|
2
|
+
end
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
5
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
6
|
+
|
7
|
+
require 'rturk/utilities'
|
8
|
+
require 'rturk/custom_operations'
|
9
|
+
require 'rturk/answer'
|
10
|
+
require 'rturk/external_question_builder'
|
11
|
+
require 'rturk/requester'
|
data/lib/rturk/answer.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module RTurk
|
2
|
+
class Answer
|
3
|
+
|
4
|
+
def self.parse(xml)
|
5
|
+
answer = XmlSimple.xml_in(xml, {'ForceArray' => false})
|
6
|
+
response = {}
|
7
|
+
answers = answer['Answer']
|
8
|
+
answers = Array.new(1) { answers } unless answers.instance_of? Array
|
9
|
+
answers.each do |a|
|
10
|
+
question = a['QuestionIdentifier']
|
11
|
+
a.delete('QuestionIdentifier')
|
12
|
+
a.each_value do |v|
|
13
|
+
response[question] = v
|
14
|
+
end
|
15
|
+
end
|
16
|
+
response
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RTurk::CustomOperations
|
2
|
+
# Overides createHIT to allow for easier entry
|
3
|
+
def create_hit(props, page)
|
4
|
+
props = format_props(props)
|
5
|
+
props = props.merge(:Question => page, :Operation => 'CreateHIT')
|
6
|
+
request(props)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Attempt to expire hit, then approve assignments, and finally dispose of
|
10
|
+
def kill_hit(hit_id)
|
11
|
+
forceExpireHIT(:HITId => hit_id)
|
12
|
+
get_assignments_for_hit(hit_id).each do |assignment|
|
13
|
+
approveAssignment(:AssignmentId => assignment[:AssignmentId])
|
14
|
+
end
|
15
|
+
disposeHIT(:HITId => hit_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Wipe out all HIT's associated with this account
|
19
|
+
def blank_slate
|
20
|
+
search_response = searchHITs(:PageSize => 100)
|
21
|
+
if search_results = search_response['SearchHITsResult']['HIT']
|
22
|
+
search_results.each do |hit|
|
23
|
+
kill_hit(hit['HITId'])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def get_assignments_for_hit(hit)
|
30
|
+
response = request(:Operation => 'GetAssignmentsForHIT', :HITId => hit)
|
31
|
+
assignments = []
|
32
|
+
if response['GetAssignmentsForHITResult']['Assignment'].instance_of?(Array)
|
33
|
+
response['GetAssignmentsForHITResult']['Assignment'].each do |assignment|
|
34
|
+
answer = RTurk::Answer.parse(assignment['Answer'])
|
35
|
+
assignment.delete('Answer')
|
36
|
+
assignment['Answer'] = answer
|
37
|
+
assignments << assignment
|
38
|
+
end
|
39
|
+
else
|
40
|
+
if assignment = response['GetAssignmentsForHITResult']['Assignment']
|
41
|
+
answer = RTurk::Answer.parse(response['GetAssignmentsForHITResult']['Assignment']['Answer'])
|
42
|
+
assignment.delete('Answer')
|
43
|
+
assignment['Answer'] = answer
|
44
|
+
assignments << assignment
|
45
|
+
end
|
46
|
+
end
|
47
|
+
assignments
|
48
|
+
end
|
49
|
+
|
50
|
+
def url_for_hit(hit_id)
|
51
|
+
url_for_hit_type(getHIT(:HITId => hit_id)[:HITTypeId])
|
52
|
+
end
|
53
|
+
|
54
|
+
def url_for_hit_type(hit_type_id)
|
55
|
+
if @host =~ /sandbox/
|
56
|
+
"http://workersandbox.mturk.com/mturk/preview?groupId=#{hit_type_id}" # Sandbox Url
|
57
|
+
else
|
58
|
+
"http://mturk.com/mturk/preview?groupId=#{hit_type_id}" # Production Url
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def format_props(params)
|
65
|
+
reward = params[:Reward]
|
66
|
+
qualifiers = params[:QualificationRequirement]
|
67
|
+
params.delete(:Reward)
|
68
|
+
params.delete(:QualificationRequirement)
|
69
|
+
params.merge!('Reward.1.Amount' => reward[:Amount], 'Reward.1.CurrencyCode' => reward[:CurrencyCode])
|
70
|
+
qualifiers.each_with_index do |qualifier, i|
|
71
|
+
params["QualificationRequirement.#{i+1}.QualificationTypeId"] = qualifier[:QualificationTypeId]
|
72
|
+
params["QualificationRequirement.#{i+1}.Comparator"] = qualifier[:Comparator]
|
73
|
+
params["QualificationRequirement.#{i+1}.IntegerValue"] = qualifier[:IntegerValue] if qualifier[:IntegerValue]
|
74
|
+
params["QualificationRequirement.#{i+1}.LocaleValue.Country"] = qualifier[:Country] if qualifier[:Country]
|
75
|
+
params["QualificationRequirement.#{i+1}.RequiredToPreview"] = qualifier[:RequiredToPreview]
|
76
|
+
end
|
77
|
+
params
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RTurk
|
2
|
+
class ExternalQuestionBuilder
|
3
|
+
|
4
|
+
|
5
|
+
def self.build(url, opts = {})
|
6
|
+
frame_height = opts[:frame_height] || 400
|
7
|
+
opts.delete(:frame_height)
|
8
|
+
querystring = opts.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')
|
9
|
+
url = opts.empty? ? url : "#{url}?#{querystring}"
|
10
|
+
xml = <<-XML
|
11
|
+
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
|
12
|
+
<ExternalURL>#{url}</ExternalURL>
|
13
|
+
<FrameHeight>#{frame_height}</FrameHeight>
|
14
|
+
</ExternalQuestion>
|
15
|
+
XML
|
16
|
+
xml
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cgi'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'base64'
|
5
|
+
require 'time'
|
6
|
+
require 'base64'
|
7
|
+
require 'digest/sha1'
|
8
|
+
require 'restclient'
|
9
|
+
require 'xmlsimple'
|
10
|
+
|
11
|
+
module RTurk
|
12
|
+
class Requester
|
13
|
+
include RTurk::Utilities
|
14
|
+
include RTurk::CustomOperations
|
15
|
+
|
16
|
+
SANDBOX = 'http://mechanicalturk.sandbox.amazonaws.com/'
|
17
|
+
PRODUCTION = 'http://mechanicalturk.amazonaws.com/'
|
18
|
+
|
19
|
+
attr_reader :access_key, :secret_key, :host
|
20
|
+
|
21
|
+
def initialize(access_key, secret_key, opts ={})
|
22
|
+
@access_key = access_key
|
23
|
+
@secret_key = secret_key
|
24
|
+
@host = opts[:sandbox] ? SANDBOX : PRODUCTION
|
25
|
+
end
|
26
|
+
|
27
|
+
def raw_request(params = {})
|
28
|
+
params = stringify_keys(params)
|
29
|
+
base_params = {
|
30
|
+
'Service'=>'AWSMechanicalTurkRequester',
|
31
|
+
'AWSAccessKeyId' => self.access_key,
|
32
|
+
'Timestamp' => Time.now.iso8601,
|
33
|
+
'Version' => '2008-08-02'
|
34
|
+
}
|
35
|
+
|
36
|
+
params.merge!(base_params)
|
37
|
+
signature = sign(params['Service'], params['Operation'], params["Timestamp"])
|
38
|
+
params['Signature'] = signature
|
39
|
+
querystring = params.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&') # order doesn't matter for the actual request
|
40
|
+
|
41
|
+
response = RestClient.get("#{self.host}?#{querystring}")
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(params = {})
|
45
|
+
response = self.raw_request(params)
|
46
|
+
XmlSimple.xml_in(response.to_s, {'ForceArray' => false})
|
47
|
+
end
|
48
|
+
|
49
|
+
def environment
|
50
|
+
@host.match(/sandbox/) ? 'sandbox' : 'production'
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_missing(method, opts)
|
54
|
+
method = method.to_s
|
55
|
+
method = method[0,1].upcase + method[1,method.size-1]
|
56
|
+
opts.merge!(:Operation => method)
|
57
|
+
request(opts)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def sign(service,method,time)
|
63
|
+
msg = "#{service}#{method}#{time}"
|
64
|
+
return hmac_sha1( self.secret_key, msg )
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
def hmac_sha1(key, s)
|
70
|
+
ipad = [].fill(0x36, 0, 64)
|
71
|
+
opad = [].fill(0x5C, 0, 64)
|
72
|
+
key = key.unpack("C*")
|
73
|
+
key += [].fill(0, 0, 64-key.length) if key.length < 64
|
74
|
+
|
75
|
+
inner = []
|
76
|
+
64.times { |i| inner.push(key[i] ^ ipad[i]) }
|
77
|
+
inner += s.unpack("C*")
|
78
|
+
|
79
|
+
outer = []
|
80
|
+
64.times { |i| outer.push(key[i] ^ opad[i]) }
|
81
|
+
outer = outer.pack("c*")
|
82
|
+
outer += Digest::SHA1.digest(inner.pack("c*"))
|
83
|
+
|
84
|
+
return Base64::encode64(Digest::SHA1.digest(outer)).chomp
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RTurk::Utilities
|
2
|
+
|
3
|
+
def camelize(phrase)
|
4
|
+
phrase.gsub!(/^[a-z]|\_+[a-z]/) { |a| a.upcase }
|
5
|
+
phrase.gsub!(/\_/, '')
|
6
|
+
return phrase
|
7
|
+
end
|
8
|
+
|
9
|
+
def stringify_keys(ahash)
|
10
|
+
ahash = ahash.inject({}) do |options, (key, value)|
|
11
|
+
options[(key.to_s rescue key) || key] = value
|
12
|
+
options
|
13
|
+
end
|
14
|
+
ahash
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/spec/answer_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe RTurk::Answer do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
@answer = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
8
|
+
<QuestionFormAnswers xmlns=\"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionFormAnswers.xsd\">
|
9
|
+
<Answer>\n<QuestionIdentifier>tweet</QuestionIdentifier>\n<FreeText>Spec example</FreeText>\n</Answer>
|
10
|
+
<Answer>\n<QuestionIdentifier>Submit</QuestionIdentifier>\n<FreeText>Submit</FreeText>\n</Answer>
|
11
|
+
<Answer>\n<QuestionIdentifier>Foo</QuestionIdentifier>\n<RandomSelector>Bar</RandomSelector>\n</Answer>
|
12
|
+
</QuestionFormAnswers>\n"
|
13
|
+
@answer2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
14
|
+
<QuestionFormAnswers xmlns=\"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionFormAnswers.xsd\">
|
15
|
+
<Answer>\n<QuestionIdentifier>tweet</QuestionIdentifier>\n<FreeText>Spec example</FreeText>\n</Answer>
|
16
|
+
</QuestionFormAnswers>\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should parse an answer" do
|
20
|
+
RTurk::Answer.parse(@answer).should == {'Submit' => 'Submit', 'tweet' => 'Spec example', 'Foo' => 'Bar'}
|
21
|
+
RTurk::Answer.parse(@answer2).should == {'tweet' => 'Spec example'}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe RTurk::ExternalQuestionBuilder do
|
5
|
+
|
6
|
+
|
7
|
+
it "should build a question with params" do
|
8
|
+
RTurk::ExternalQuestionBuilder.build('http://google.com/', :id => 'foo').should ==
|
9
|
+
<<-XML
|
10
|
+
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
|
11
|
+
<ExternalURL>http://google.com/?id=foo</ExternalURL>
|
12
|
+
<FrameHeight>400</FrameHeight>
|
13
|
+
</ExternalQuestion>
|
14
|
+
XML
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should build a question without params" do
|
18
|
+
RTurk::ExternalQuestionBuilder.build('http://google.com/').should ==
|
19
|
+
<<-XML
|
20
|
+
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
|
21
|
+
<ExternalURL>http://google.com/</ExternalURL>
|
22
|
+
<FrameHeight>400</FrameHeight>
|
23
|
+
</ExternalQuestion>
|
24
|
+
XML
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Your AWSAccessKeyId ( leave commented to use global default )
|
2
|
+
AWSAccessKeyId: 0000000000000000000000S
|
3
|
+
|
4
|
+
# Your AWSAccessKey ( leave commented to use global default )
|
5
|
+
AWSAccessKey: YOURSECRETACCESSKEYGOESHERE
|
6
|
+
|
7
|
+
# Host to talk to ( Prod or Sandbox )
|
8
|
+
Host: Sandbox
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RTurk::Requester do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
aws = YAML.load(File.open(File.join(SPEC_ROOT, 'mturk.yml')))
|
7
|
+
@turk = RTurk::Requester.new(aws['AWSAccessKeyId'], aws['AWSAccessKey'], :sandbox => true)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should perform raw operations" do
|
11
|
+
response = @turk.request(:Operation => 'GetHIT', 'HITId' => 'test')
|
12
|
+
response['HIT']['Request'].include?('Errors').should be_true
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should also interpret methods as operations" do
|
16
|
+
@turk.getHIT(:HITId => 'test')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return its environment" do
|
20
|
+
@turk.environment.should == 'sandbox'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return its environment as production, the default" do
|
24
|
+
aws = YAML.load(File.open(File.join(SPEC_ROOT, 'mturk.yml')))
|
25
|
+
@turk = RTurk::Requester.new(aws['AWSAccessKeyId'], aws['AWSAccessKey'])
|
26
|
+
@turk.environment.should == 'production'
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rturk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Percival
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-09 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rest-client
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0.9"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: xml-simple
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.12
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: mark@mpercival.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.markdown
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- README.markdown
|
47
|
+
- VERSION
|
48
|
+
- examples/blank_slate.rb
|
49
|
+
- examples/external_page.rb
|
50
|
+
- examples/mturk.sample.yml
|
51
|
+
- examples/newtweet.html
|
52
|
+
- examples/review_answer.rb
|
53
|
+
- lib/rturk.rb
|
54
|
+
- lib/rturk/answer.rb
|
55
|
+
- lib/rturk/custom_operations.rb
|
56
|
+
- lib/rturk/external_question_builder.rb
|
57
|
+
- lib/rturk/requester.rb
|
58
|
+
- lib/rturk/utilities.rb
|
59
|
+
- spec/answer_spec.rb
|
60
|
+
- spec/external_question_spec.rb
|
61
|
+
- spec/mturk.sample.yml
|
62
|
+
- spec/requester_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
- LICENSE
|
65
|
+
has_rdoc: true
|
66
|
+
homepage: http://github.com/markpercival/rturk
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options:
|
71
|
+
- --charset=UTF-8
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
version:
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.3.5
|
90
|
+
signing_key:
|
91
|
+
specification_version: 2
|
92
|
+
summary: Mechanical Turk API Wrapper
|
93
|
+
test_files:
|
94
|
+
- spec/answer_spec.rb
|
95
|
+
- spec/external_question_spec.rb
|
96
|
+
- spec/requester_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
- examples/blank_slate.rb
|
99
|
+
- examples/external_page.rb
|
100
|
+
- examples/review_answer.rb
|