1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
|
//
// goamz - Go packages to interact with the Amazon Web Services.
//
// https://wiki.ubuntu.com/goamz
//
// Copyright (c) 2011 Canonical Ltd.
//
// Written by Graham Miller <graham.miller@gmail.com>
// This package is in an experimental state, and does not currently
// follow conventions and style of the rest of goamz or common
// Go conventions. It must be polished before it's considered a
// first-class package in goamz.
package mturk
import (
"encoding/xml"
"errors"
"fmt"
"github.com/docker/goamz/aws"
"net/http"
//"net/http/httputil"
"net/url"
"strconv"
"strings"
"time"
)
type MTurk struct {
aws.Auth
URL *url.URL
}
func New(auth aws.Auth, sandbox bool) *MTurk {
mt := &MTurk{Auth: auth}
var err error
if sandbox {
mt.URL, err = url.Parse("https://mechanicalturk.sandbox.amazonaws.com/")
} else {
mt.URL, err = url.Parse("https://mechanicalturk.amazonaws.com/")
}
if err != nil {
panic(err.Error())
}
return mt
}
// ----------------------------------------------------------------------------
// Request dispatching logic.
// Error encapsulates an error returned by MTurk.
type Error struct {
StatusCode int // HTTP status code (200, 403, ...)
Code string // EC2 error code ("UnsupportedOperation", ...)
Message string // The human-oriented error message
RequestId string
}
func (err *Error) Error() string {
return err.Message
}
// The request stanza included in several response types, for example
// in a "CreateHITResponse". http://goo.gl/qGeKf
type xmlRequest struct {
RequestId string
IsValid string
Errors []Error `xml:"Errors>Error"`
}
/*
A Price represents an amount of money in a given currency.
Reference:
http://docs.aws.amazon.com/AWSMechTurk/latest/AWSMturkAPI/ApiReference_PriceDataStructureArticle.html
*/
type Price struct {
// The amount of money, as a number. The amount is in the currency specified
// by the CurrencyCode. For example, if CurrencyCode is USD, the amount will
// be in United States dollars (e.g. 12.75 is $12.75 US).
Amount string
// A code that represents the country and units of the currency. Its value is
// Type an ISO 4217 currency code, such as USD for United States dollars.
//
// Constraints: Currently only USD is supported.
CurrencyCode string
// A textual representation of the price, using symbols and formatting
// appropriate for the currency. Symbols are represented using the Unicode
// character set. You do not need to specify FormattedPrice in a request.
// It is only provided by the service in responses, as a convenience to
// your application.
FormattedPrice string
}
/*
Really just a country string.
Reference:
- http://docs.aws.amazon.com/AWSMechTurk/latest/AWSMturkAPI/ApiReference_LocaleDataStructureArticle.html
- http://www.iso.org/iso/country_codes/country_codes
*/
type Locale string
/*
A QualificationRequirement describes a Qualification a Worker must
have before the Worker is allowed to accept a HIT. A requirement may optionally
state that a Worker must have the Qualification to preview the HIT.
Reference:
http://docs.aws.amazon.com/AWSMechTurk/latest/AWSMturkAPI/ApiReference_QualificationRequirementDataStructureArticle.html
*/
type QualificationRequirement struct {
// The ID of the Qualification type for the requirement.
// See http://docs.aws.amazon.com/AWSMechTurk/latest/AWSMturkAPI/ApiReference_QualificationRequirementDataStructureArticle.html#ApiReference_QualificationType-IDs
QualificationTypeId string
// The kind of comparison to make against a Qualification's value.
// Two values can be compared to see if one value is "LessThan",
// "LessThanOrEqualTo", "GreaterThan", "GreaterThanOrEqualTo", "EqualTo", or
// "NotEqualTo" the other. A Qualification requirement can also test if a
// Qualification "Exists" in the user's profile, regardless of its value.
Comparator string
// The integer value to compare against the Qualification's value.
IntegerValue int
// The locale value to compare against the Qualification's value, if the
// Qualification being compared is the locale Qualification.
LocaleValue Locale
// If true, the question data for the HIT will not be shown when a Worker
// whose Qualifications do not meet this requirement tries to preview the HIT.
// That is, a Worker's Qualifications must meet all of the requirements for
// which RequiredToPreview is true in order to preview the HIT.
//
// If a Worker meets all of the requirements where RequiredToPreview is true
// (or if there are no such requirements), but does not meet all of the
// requirements for the HIT, the Worker will be allowed to preview the HIT's
// question data, but will not be allowed to accept and complete the HIT.
RequiredToPreview bool
}
// Data structure holding the contents of an "external"
// question. http://goo.gl/NP8Aa
type ExternalQuestion struct {
XMLName xml.Name `xml:"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd ExternalQuestion"`
ExternalURL string
FrameHeight int
}
// Holds the html content of the HTMLQuestion.
type HTMLContent struct {
Content string `xml:",innerxml"`
}
// Data structure holding the contents of an "html"
// question. http://goo.gl/hQn5An
type HTMLQuestion struct {
XMLName xml.Name `xml:"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2011-11-11/HTMLQuestion.xsd HTMLQuestion"`
HTMLContent HTMLContent
FrameHeight int
}
// The data structure representing a "human interface task" (HIT)
// Currently only supports "external" questions, because Go
// structs don't support union types. http://goo.gl/NP8Aa
// This type is returned, for example, from SearchHITs
// http://goo.gl/PskcX
type HIT struct {
Request xmlRequest
HITId string
HITTypeId string
CreationTime string
Title string
Description string
Keywords string
HITStatus string
Reward Price
LifetimeInSeconds uint
AssignmentDurationInSeconds uint
MaxAssignments uint
AutoApprovalDelayInSeconds uint
QualificationRequirement QualificationRequirement
Question interface{}
RequesterAnnotation string
NumberofSimilarHITs uint
HITReviewStatus string
NumberOfAssignmentsPending uint
NumberOfAssignmentsAvailable uint
NumberOfAssignmentsCompleted uint
}
// The main data structure returned by SearchHITs
// http://goo.gl/PskcX
type SearchHITsResult struct {
NumResults uint
PageNumber uint
TotalNumResults uint
HITs []HIT `xml:"HIT"`
}
// The wrapper data structure returned by SearchHITs
// http://goo.gl/PskcX
type SearchHITsResponse struct {
RequestId string `xml:"OperationRequest>RequestId"`
SearchHITsResult SearchHITsResult
}
// The wrapper data structure returned by CreateHIT
// http://goo.gl/PskcX
type CreateHITResponse struct {
RequestId string `xml:"OperationRequest>RequestId"`
HIT HIT
}
type Assignment struct {
AssignmentId string
WorkerId string
HITId string
AssignmentStatus string
AutoApprovalTime string
AcceptTime string
SubmitTime string
ApprovalTime string
Answer string
}
func (a Assignment) Answers() (answers map[string]string) {
answers = make(map[string]string)
decoder := xml.NewDecoder(strings.NewReader(a.Answer))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
if startElement.Name.Local == "Answer" {
var answer struct {
QuestionIdentifier string
FreeText string
}
decoder.DecodeElement(&answer, &startElement)
answers[answer.QuestionIdentifier] = answer.FreeText
}
}
}
return
}
type GetAssignmentsForHITResponse struct {
RequestId string `xml:"OperationRequest>RequestId"`
GetAssignmentsForHITResult struct {
Request xmlRequest
NumResults uint
TotalNumResults uint
PageNumber uint
Assignments []Assignment `xml:"Assignment"`
}
}
type GetHITResponse struct {
RequestId string `xml:"OperationRequest>RequestId"`
HIT HIT
}
/*
CreateHIT corresponds to the "CreateHIT" operation of the Mechanical Turk
API. Currently only supports "external" questions (see "HIT" struct above).
Here are the detailed description for the parameters:
title Required. A title should be short and descriptive about the
kind of task the HIT contains. On the Amazon Mechanical Turk
web site, the HIT title appears in search results, and
everywhere the HIT is mentioned.
description Required. A description includes detailed information about the
kind of task the HIT contains. On the Amazon Mechanical Turk
web site, the HIT description appears in the expanded view of
search results, and in the HIT and assignment screens. A good
description gives the user enough information to evaluate the
HIT before accepting it.
question Required. The data the person completing the HIT uses to produce
the results. Consstraints: Must be a QuestionForm data structure,
an ExternalQuestion data structure, or an HTMLQuestion data
structure. The XML question data must not be larger than 64
kilobytes (65,535 bytes) in size, including whitespace.
reward Required. The amount of money the Requester will pay a Worker
for successfully completing the HIT.
assignmentDurationInSeconds Required. The amount of time, in seconds, that
a Worker has to complete the HIT after accepting
it. If a Worker does not complete the assignment
within the specified duration, the assignment is
considered abandoned. If the HIT is still
active (that is, its lifetime has not elapsed),
the assignment becomes available for other users
to find and accept. Valid Values: any integer
between 30 (30 seconds) and 31536000 (365 days).
lifetimeInSeconds Required. An amount of time, in seconds, after which the
HIT is no longer available for users to accept. After
the lifetime of the HIT elapses, the HIT no longer
appears in HIT searches, even if not all of the
assignments for the HIT have been accepted. Valid Values:
any integer between 30 (30 seconds) and 31536000 (365 days).
keywords One or more words or phrases that describe the HIT,
separated by commas. These words are used in searches to
find HITs. Constraints: cannot be more than 1,000
characters.
maxAssignments The number of times the HIT can be accepted and completed
before the HIT becomes unavailable. Valid Values: any
integer between 1 and 1000000000 (1 billion). Default: 1
qualificationRequirement A condition that a Worker's Qualifications must
meet before the Worker is allowed to accept and
complete the HIT. Constraints: no more than 10
QualificationRequirement for each HIT.
requesterAnnotation An arbitrary data field. The RequesterAnnotation
parameter lets your application attach arbitrary data to
the HIT for tracking purposes. For example, the
RequesterAnnotation parameter could be an identifier
internal to the Requester's application that corresponds
with the HIT. Constraints: must not be longer than 255
characters in length.
Reference:
http://docs.aws.amazon.com/AWSMechTurk/latest/AWSMturkAPI/ApiReference_CreateHITOperation.html
*/
func (mt *MTurk) CreateHIT(
title, description string,
question interface{},
reward Price,
assignmentDurationInSeconds,
lifetimeInSeconds uint,
keywords string,
maxAssignments uint,
qualificationRequirement *QualificationRequirement,
requesterAnnotation string,
addParams ...string) (h *HIT, err error) {
params := make(map[string]string)
params["Title"] = title
params["Description"] = description
params["Question"], err = xmlEncode(&question)
if err != nil {
return
}
params["Reward.1.Amount"] = reward.Amount
params["Reward.1.CurrencyCode"] = reward.CurrencyCode
params["AssignmentDurationInSeconds"] = strconv.FormatUint(uint64(assignmentDurationInSeconds), 10)
params["LifetimeInSeconds"] = strconv.FormatUint(uint64(lifetimeInSeconds), 10)
if keywords != "" {
params["Keywords"] = keywords
}
if maxAssignments != 0 {
params["MaxAssignments"] = strconv.FormatUint(uint64(maxAssignments), 10)
}
if qualificationRequirement != nil {
params["QualificationRequirement"], err = xmlEncode(qualificationRequirement)
if err != nil {
return
}
}
if requesterAnnotation != "" {
params["RequesterAnnotation"] = requesterAnnotation
}
var response CreateHITResponse
err = mt.query(params, "CreateHIT", &response, addParams...)
if err == nil {
h = &response.HIT
}
return
}
// Corresponds to the "CreateHIT" operation of the Mechanical Turk
// API, using an existing "hit type". http://goo.gl/cDBRc Currently only
// supports "external" questions (see "HIT" struct above). If
// "maxAssignments" or "requesterAnnotation" are the zero value for
// their types, they will not be included in the request.
func (mt *MTurk) CreateHITOfType(hitTypeId string,
q ExternalQuestion,
lifetimeInSeconds uint,
maxAssignments uint,
requesterAnnotation string,
addParams ...string) (h *HIT, err error) {
params := make(map[string]string)
params["HITTypeId"] = hitTypeId
params["Question"], err = xmlEncode(&q)
if err != nil {
return
}
params["LifetimeInSeconds"] = strconv.FormatUint(uint64(lifetimeInSeconds), 10)
if maxAssignments != 0 {
params["MaxAssignments"] = strconv.FormatUint(uint64(maxAssignments), 10)
}
if requesterAnnotation != "" {
params["RequesterAnnotation"] = requesterAnnotation
}
var response CreateHITResponse
err = mt.query(params, "CreateHIT", &response, addParams...)
if err == nil {
h = &response.HIT
}
return
}
// Get the Assignments for a HIT.
func (mt *MTurk) GetAssignmentsForHIT(hitId string, addParams ...string) (r []Assignment, err error) {
params := make(map[string]string)
params["HITId"] = hitId
var response GetAssignmentsForHITResponse
err = mt.query(params, "GetAssignmentsForHIT", &response, addParams...)
if err == nil {
r = response.GetAssignmentsForHITResult.Assignments
}
return
}
// Get a single HIT
func (mt *MTurk) GetHIT(hitId string, addParams ...string) (h *HIT, err error) {
params := make(map[string]string)
params["HITId"] = hitId
var response GetHITResponse
err = mt.query(params, "GetHIT", &response, addParams...)
if err == nil {
h = &response.HIT
}
return
}
// Corresponds to "SearchHITs" operation of Mechanical Turk. http://goo.gl/PskcX
// Currenlty supports none of the optional parameters.
func (mt *MTurk) SearchHITs(addParams ...string) (s *SearchHITsResult, err error) {
params := make(map[string]string)
var response SearchHITsResponse
err = mt.query(params, "SearchHITs", &response, addParams...)
if err == nil {
s = &response.SearchHITsResult
}
return
}
// Adds common parameters to the "params" map, signs the request,
// adds the signature to the "params" map and sends the request
// to the server. It then unmarshals the response in to the "resp"
// parameter using xml.Unmarshal()
func (mt *MTurk) query(params map[string]string, operation string, resp interface{}, addParams ...string) error {
service := "AWSMechanicalTurkRequester"
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z")
// Add additional parameters to the map of params
for i := 1; i < len(addParams); i += 2 {
params[addParams[i-1]] = addParams[i]
}
params["AWSAccessKeyId"] = mt.Auth.AccessKey
params["Service"] = service
params["Timestamp"] = timestamp
params["Operation"] = operation
// make a copy
url := *mt.URL
sign(mt.Auth, service, operation, timestamp, params)
url.RawQuery = multimap(params).Encode()
r, err := http.Get(url.String())
if err != nil {
return err
}
//dump, _ := httputil.DumpResponse(r, true)
//println("DUMP:\n", string(dump))
if r.StatusCode != 200 {
return errors.New(fmt.Sprintf("%d: unexpected status code", r.StatusCode))
}
dec := xml.NewDecoder(r.Body)
err = dec.Decode(resp)
r.Body.Close()
return err
}
func multimap(p map[string]string) url.Values {
q := make(url.Values, len(p))
for k, v := range p {
q[k] = []string{v}
}
return q
}
func xmlEncode(i interface{}) (s string, err error) {
var buf []byte
buf, err = xml.Marshal(i)
if err != nil {
return
}
s = string(buf)
return
}
|