rturk 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|