- // ==UserScript==
- // @name [MTurk Worker] HIT Exporter for Slack
- // @namespace https://github.com/Kadauchi
- // @version 1.0.31
- // @description Allows you to export HITs as formatted text with short, plain, bbcode or markdown styling.
- // @author Kadauchi
- // @icon http://i.imgur.com/oGRQwPN.png
- // @include https://worker.mturk.com/*
- // @grant GM_setClipboard
- // ==/UserScript==
-
- /* globals GM_setClipboard */
-
- const hitExports = `all` // Valid options are: `all`, `short`, `plain`, `bbcode` or `markdown`
- const turkerview = true // Use turkerview in HIT exports
- const turkopticon = true // Use turkopticon in HIT exports
- const turkopticon2 = true // Use turkopticon2 in HIT exports
-
- async function short (event, object) {
- window.alert(`Short exports are not supported yet`)
- }
-
- async function plain (event, object) {
- const hit = object || JSON.parse(event.target.dataset.hit)
- const requesterReview = await getRequesterReview(hit.requester_id)
- const reviewsTemplate = []
-
- if (requesterReview.turkerview !== undefined) {
- const tv = requesterReview.turkerview
- const tvRatings = tv.ratings
-
- reviewsTemplate.push([
- `TV:`,
- `[Hrly: ${tvRatings.hourly}]`,
- `[Pay: ${tvRatings.pay}]`,
- `[Fast: ${tvRatings.fast}]`,
- `[Comm: ${tvRatings.comm}]`,
- `[Rej: ${tv.rejections}]`,
- `[ToS: ${tv.tos}]`,
- `[Blk: ${tv.blocks}]`,
- `• https://turkerview.com/requesters/${hit.requester_id}`
-
- ].join(` `))
- } else if (turkerview === true) {
- reviewsTemplate.push(`TV: No Reviews • https://turkerview.com/requesters/${hit.requester_id}`)
- }
-
- if (requesterReview.turkopticon !== undefined) {
- const to = requesterReview.turkopticon
- const toAttrs = to.attrs
-
- reviewsTemplate.push([
- `TO:`,
- `[Pay: ${toAttrs.pay}]`,
- `[Fast: ${toAttrs.fast}]`,
- `[Comm: ${toAttrs.comm}]`,
- `[Fair: ${toAttrs.fair}]`,
- `[Reviews: ${to.reviews}]`,
- `[ToS: ${to.tos_flags}]`,
- `• https://turkopticon.ucsd.edu/${hit.requester_id}`
- ].join(` `))
- } else if (turkopticon === true) {
- reviewsTemplate.push(`TO: No Reviews • https://turkopticon.ucsd.edu/${hit.requester_id}`)
- }
-
- if (requesterReview.turkopticon2 !== undefined) {
- const to2 = requesterReview.turkopticon2
- const to2Recent = to2.recent
-
- reviewsTemplate.push([
- `TO2:`,
- `[Hrly: ${to2Recent.reward[1] > 0 ? `${(to2Recent.reward[0] / to2Recent.reward[1] * 3600).toMoneyString()}` : `---`}]`,
- `[Pen: ${to2Recent.pending > 0 ? `${(to2Recent.pending / 86400).toFixed(2)} days` : `---`}]`,
- `[Res: ${to2Recent.comm[1] > 0 ? `${Math.round(to2Recent.comm[0] / to2Recent.comm[1] * 100)}% of ${to2Recent.comm[1]}` : `---`}]`,
- `[Rec: ${to2Recent.recommend[1] > 0 ? `${Math.round(to2Recent.recommend[0] / to2Recent.recommend[1] * 100)}% of ${to2Recent.recommend[1]}` : `---`}]`,
- `[Rej: ${to2Recent.rejected[0]}]`,
- `[ToS: ${to2Recent.tos[0]}]`,
- `[Brk: ${to2Recent.broken[0]}]`,
- `https://turkopticon.info/requesters/${hit.requester_id}`
- ].join(` `))
- } else if (turkopticon2 === true) {
- reviewsTemplate.push(`TO2: No Reviews • https://turkopticon.info/requesters/${hit.rid}`)
- }
-
- const exportTemplate = [
- `Title: ${hit.title} • https://worker.mturk.com/projects/${hit.hit_set_id}/tasks • https://worker.mturk.com/projects/${hit.hit_set_id}/tasks/accept_random`,
- `Requester: ${hit.requester_name} • https://worker.mturk.com/requesters/${hit.requester_id}/projects`,
- reviewsTemplate.join(`\n`),
- `Reward: ${hit.monetary_reward.amount_in_dollars.toMoneyString()}`,
- `Duration: ${hit.assignment_duration_in_seconds.toTimeString()}`,
- `Available: ${hit.assignable_hits_count}`,
- `Description: ${hit.description}`,
- `Qualifications: ${hit.project_requirements.map(o => `${o.qualification_type.name} ${o.comparator} ${o.qualification_values.map(v => v).join(`, `)}`.trim()).join(`; `)}`
- ].filter((item) => item !== undefined).join(`\n`)
-
- GM_setClipboard(exportTemplate)
-
- const notification = new window.Notification(`Plain HIT Export has been copied to your clipboard.`)
- setTimeout(notification.close.bind(notification), 10000)
- }
-
- async function bbcode (event, object) {
- const hit = object || JSON.parse(event.target.dataset.hit)
- const requesterReview = await getRequesterReview(hit.requester_id)
- const reviewsTemplate = []
-
- const ratingColor = (rating) => {
- if (rating > 3.99) {
- return `[color=#00cc00]${rating}[/color]`
- } else if (rating > 2.99) {
- return `[color=#cccc00]${rating}[/color]`
- } else if (rating > 1.99) {
- return `[color=#cc6600]${rating}[/color]`
- } else if (rating > 0.00) {
- return `[color=#cc0000]${rating}[/color]`
- }
- return rating
- }
-
- const percentColor = (rating) => {
- if (rating[1] > 0) {
- const percent = Math.round(rating[0] / rating[1] * 100)
-
- if (percent > 79) {
- return `[color=#00cc00]${percent}%[/color] of ${rating[1]}`
- } else if (percent > 59) {
- return `[color=#cccc00]${percent}%[/color] of ${rating[1]}`
- } else if (percent > 39) {
- return `[color=#cc6600]${percent}%[/color] of ${rating[1]}`
- }
- return `[color=#cc0000]${percent}%[/color] of ${rating[1]}`
- }
- return `---`
- }
-
- const goodBadColor = (rating) => {
- return `[color=${rating === 0 ? `#00cc00` : `#cc0000`}]${rating}[/color]`
- }
-
- if (requesterReview.turkerview !== undefined) {
- const tv = requesterReview.turkerview
-
- reviewsTemplate.push([
- `[b][url=https://turkerview.com/requesters/${hit.requester_id}]TV[/url]:`,
- `[Hrly: ${tv.ratings.hourly}]`,
- `[Pay: ${ratingColor(tv.ratings.pay)}]`,
- `[Fast: ${ratingColor(tv.ratings.fast)}]`,
- `[Comm: ${ratingColor(tv.ratings.comm)}]`,
- `[Rej: ${goodBadColor(tv.rejections)}]`,
- `[ToS: ${goodBadColor(tv.tos)}]`,
- `[Blk: ${goodBadColor(tv.blocks)}][/b]`
- ].join(` `))
- } else if (turkerview === true) {
- reviewsTemplate.push(`[b][url=https://turkerview.com/requesters/${hit.requester_id}]TV[/url]:[/b] No Reviews`)
- }
-
- if (requesterReview.turkopticon !== undefined) {
- const to = requesterReview.turkopticon
- const toAttrs = to.attrs
-
- if (toAttrs) {
- reviewsTemplate.push([
- `[b][url=https://turkopticon.ucsd.edu/${hit.requester_id}]TO[/url]:`,
- `[Pay: ${ratingColor(toAttrs.pay)}]`,
- `[Fast: ${ratingColor(toAttrs.fast)}]`,
- `[Comm: ${ratingColor(toAttrs.comm)}]`,
- `[Fair: ${ratingColor(toAttrs.fair)}]`,
- `[Reviews: ${to.reviews}]`,
- `[ToS: ${goodBadColor(to.tos_flags)}][/b]`
- ].join(` `))
- } else {
- reviewsTemplate.push(`[b][url=https://turkopticon.ucsd.edu/${hit.requester_id}]TO[/url]:[/b] No Reviews`)
- }
- } else if (turkopticon === true) {
- reviewsTemplate.push(`[b][url=https://turkopticon.ucsd.edu/${hit.requester_id}]TO[/url]:[/b] No Reviews`)
- }
-
- if (requesterReview.turkopticon2 !== undefined) {
- const to2 = requesterReview.turkopticon2
- const to2Recent = to2.recent
-
- reviewsTemplate.push([
- `[b][url=https://turkopticon.info/requesters/${hit.requester_id}]TO2[/url]:`,
- `[Hrly: ${to2Recent.reward[1] > 0 ? `${(to2Recent.reward[0] / to2Recent.reward[1] * 3600).toMoneyString()}` : `---`}]`,
- `[Pen: ${to2Recent.pending > 0 ? `${(to2Recent.pending / 86400).toFixed(2)} days` : `---`}]`,
- `[Res: ${percentColor(to2Recent.comm)}]`,
- `[Rec: ${percentColor(to2Recent.recommend)}]`,
- `[Rej: ${goodBadColor(to2Recent.rejected[0])}]`,
- `[ToS: ${goodBadColor(to2Recent.tos[0])}]`,
- `[Brk: ${goodBadColor(to2Recent.broken[0])}][/b]`
- ].join(` `))
- } else if (turkopticon2 === true) {
- reviewsTemplate.push(`[b][url=https://turkopticon.info/requesters/${hit.requester_id}]TO2[/url]:[/b] No Reviews`)
- }
-
- const exportTemplate = [
- `[b]Title:[/b] [url=https://worker.mturk.com/projects/${hit.hit_set_id}/tasks]${hit.title}[/url] | [url=https://worker.mturk.com/projects/${hit.hit_set_id}/tasks/accept_random]PANDA[/url]`,
- `[b]Requester:[/b] [url=https://worker.mturk.com/requesters/${hit.requester_id}/projects]${hit.requester_name}[/url] [${hit.requester_id}] ([url=https://worker.mturk.com/requesters/${hit.requester_id}]Contact[/url])`,
- reviewsTemplate.join(`\n`),
- `[b]Reward:[/b] ${hit.monetary_reward.amount_in_dollars.toMoneyString()}`,
- `[b]Duration:[/b] ${hit.assignment_duration_in_seconds.toTimeString()}`,
- `[b]Available:[/b] ${hit.assignable_hits_count}`,
- `[b]Description:[/b] ${hit.description}`,
- `[b]Qualifications:[/b] ${hit.project_requirements.map(o => `${o.qualification_type.name} ${o.comparator} ${o.qualification_values.map(v => v).join(`, `)}`.trim()).join(`; `)}`
- ].filter((item) => item !== undefined).join(`\n`)
-
- GM_setClipboard(`[table][tr][td]${exportTemplate}[/td][/tr][/table]`)
-
- const notification = new window.Notification(`BBCode HIT Export has been copied to your clipboard.`)
- setTimeout(notification.close.bind(notification), 10000)
- }
-
- async function markdown (event, object) {
- const hit = object || JSON.parse(event.target.dataset.hit)
- const requesterReview = await getRequesterReview(hit.requester_id)
- const reviewsTemplate = []
-
- if (requesterReview.turkerview !== undefined) {
- const tv = requesterReview.turkerview
- const tvRatings = tv.ratings
-
- reviewsTemplate.push([
- `**[TV](https://turkerview.com/requesters/${hit.requester_id}):**`,
- `[Hrly: ${tvRatings.hourly}]`,
- `[Pay: ${tvRatings.pay}]`,
- `[Fast: ${tvRatings.fast}]`,
- `[Comm: ${tvRatings.comm}]`,
- `[Rej: ${tv.rejections}]`,
- `[ToS: ${tv.tos}]`,
- `[Blk: ${tv.blocks}]`
- ].join(` `))
- } else if (turkerview === true) {
- reviewsTemplate.push(`TV: No Reviews • https://turkerview.com/requesters/${hit.requester_id}`)
- }
-
- if (requesterReview.turkopticon !== undefined) {
- const to = requesterReview.turkopticon
- const toAttrs = to.attrs
-
- reviewsTemplate.push([
- `**[TO](https://turkopticon.ucsd.edu/${hit.requester_id}):**`,
- `[Pay: ${toAttrs.pay}]`,
- `[Fast: ${toAttrs.fast}]`,
- `[Comm: ${toAttrs.comm}]`,
- `[Fair: ${toAttrs.fair}]`,
- `[Reviews: ${to.reviews}]`,
- `[ToS: ${to.tos_flags}]`
- ].join(` `))
- } else if (turkopticon === true) {
- reviewsTemplate.push(`TO: No Reviews • https://turkopticon.ucsd.edu/${hit.requester_id}`)
- }
-
- if (requesterReview.turkopticon2 !== undefined) {
- const to2 = requesterReview.turkopticon2
- const to2Recent = to2.recent
-
- reviewsTemplate.push([
- `**[TO2](https://turkopticon.info/requesters/${hit.requester_id}):**`,
- `[Hrly: ${to2Recent.reward[1] > 0 ? `${(to2Recent.reward[0] / to2Recent.reward[1] * 3600).toMoneyString()}` : `---`}]`,
- `[Pen: ${to2Recent.pending > 0 ? `${(to2Recent.pending / 86400).toFixed(2)} days` : `---`}]`,
- `[Res: ${to2Recent.comm[1] > 0 ? `${Math.round(to2Recent.comm[0] / to2Recent.comm[1] * 100)}% of ${to2Recent.comm[1]}` : `---`}]`,
- `[Rec: ${to2Recent.recommend[1] > 0 ? `${Math.round(to2Recent.recommend[0] / to2Recent.recommend[1] * 100)}% of ${to2Recent.recommend[1]}` : `---`}]`,
- `[Rej: ${to2Recent.rejected[0]}]`,
- `[ToS: ${to2Recent.tos[0]}]`,
- `[Brk: ${to2Recent.broken[0]}]`,
- ``
- ].join(` `))
- } else if (turkopticon2 === true) {
- reviewsTemplate.push(`TO2: No Reviews • https://turkopticon.info/requesters/${hit.rid}`)
- }
-
- const exportTemplate = [
- `> **Title:** [${hit.title}](https://worker.mturk.com/projects/${hit.hit_set_id}/tasks) | [PANDA](https://worker.mturk.com/projects/${hit.hit_set_id}/tasks/accept_random)`,
- `**Requester:** [${hit.requester_name}](https://worker.mturk.com/requesters${hit.requester_id}/projects) [${hit.requester_id}] ([Contact](https://worker.mturk.com/contact?requesterId=${hit.requester_id}))`,
- reviewsTemplate.join(` \n`),
- `**Reward:** ${hit.monetary_reward.amount_in_dollars.toMoneyString()}`,
- `**Duration:** ${hit.assignment_duration_in_seconds.toTimeString()}`,
- `**Available:** ${hit.assignable_hits_count}`,
- `**Description:** ${hit.description}`,
- `**Qualifications:** ${hit.project_requirements.map(o => `${o.qualification_type.name} ${o.comparator} ${o.qualification_values.map(v => v).join(`, `)}`.trim()).join(`; `)}`
- ]
- .filter((item) => item !== undefined).join(` \n`)
-
- GM_setClipboard(exportTemplate)
-
- const notification = new window.Notification(`Markdown HIT Export has been copied to your clipboard.`)
- setTimeout(notification.close.bind(notification), 10000)
- }
-
- async function getRequesterReview (id) {
- return new Promise(async (resolve) => {
- const getReview = (stringSite, stringURL) => {
- return new Promise(async (resolve) => {
- try {
- const response = await window.fetch(stringURL)
-
- if (response.status === 200) {
- const json = await response.json()
- resolve([stringSite, json.data ? Object.assign(...json.data.map((item) => ({ [item.id]: item.attributes.aggregates }))) : json])
- } else {
- resolve()
- }
- } catch (error) {
- resolve()
- }
- })
- }
-
- const promises = []
-
- if (turkerview === true) {
- promises.push(getReview(`turkerview`, `https://turkerview.com/api/v1/requesters/?ids=${id}`))
- }
- if (turkopticon === true) {
- promises.push(getReview(`turkopticon`, `https://turkopticon.ucsd.edu/api/multi-attrs.php?ids=${id}`))
- }
- if (turkopticon2 === true) {
- promises.push(getReview(`turkopticon2`, `https://api.turkopticon.info/requesters?rids=${id}&fields[requesters]=aggregates`))
- }
-
- const getReviewAll = await Promise.all(promises)
-
- const objectReview = {}
-
- for (const item of getReviewAll) {
- if (item && item.length > 0) {
- const site = item[0]
- const reviews = item[1]
-
- for (const key in reviews) {
- objectReview[site] = reviews[key]
- }
- }
- }
- resolve(objectReview)
- })
- }
-
- (function () {
- const react = document.querySelector(`div[data-react-class="require('reactComponents/hitSetTable/HitSetTable')['default']"]`) ||
- document.querySelector(`div[data-react-class="require('reactComponents/taskQueueTable/TaskQueueTable')['default']"]`)
-
- if (react) {
- const hitExportButton = (text, callback) => {
- const div = document.createElement(`div`)
- div.className = `col-xs-6`
-
- const button = document.createElement(`button`)
- button.className = `btn btn-primary btn-hit-export`
- button.textContent = text
- button.style.width = `100%`
- button.addEventListener(`click`, callback)
- div.appendChild(button)
-
- return div
- }
-
- const modal = document.createElement(`div`)
- modal.className = `modal`
- modal.id = `hitExportModal`
- document.body.appendChild(modal)
-
- const modalDialog = document.createElement(`div`)
- modalDialog.className = `modal-dialog`
- modal.appendChild(modalDialog)
-
- const modalContent = document.createElement(`div`)
- modalContent.className = `modal-content`
- modalDialog.appendChild(modalContent)
-
- const modalHeader = document.createElement(`div`)
- modalHeader.className = `modal-header`
- modalContent.appendChild(modalHeader)
-
- // modal close here
-
- const modalTitle = document.createElement(`h2`)
- modalTitle.className = `modal-title`
- modalTitle.textContent = `HIT Export`
- modalHeader.appendChild(modalTitle)
-
- const modalBody = document.createElement(`div`)
- modalBody.className = `modal-body`
- modalContent.appendChild(modalBody)
-
- const modalBodyRow1 = document.createElement(`div`)
- modalBodyRow1.className = `row`
- modalBody.appendChild(modalBodyRow1)
- modalBodyRow1.appendChild(hitExportButton(`Short`, short))
- modalBodyRow1.appendChild(hitExportButton(`Plain`, plain))
-
- const modalBodyRow2 = document.createElement(`div`)
- modalBodyRow2.className = `row`
- modalBody.appendChild(modalBodyRow2)
- modalBodyRow2.appendChild(hitExportButton(`BBCode`, bbcode))
- modalBodyRow2.appendChild(hitExportButton(`Markdown`, markdown))
-
- const style = document.createElement(`style`)
- style.innerHTML = `.modal-backdrop.in { z-index: 1049; }`
- document.head.appendChild(style)
-
- const json = JSON.parse(react.dataset.reactProps).bodyData
- const hitRows = react.getElementsByClassName(`table-row`)
-
- for (let i = 0; i < hitRows.length; i++) {
- const hit = json[i].project ? json[i].project : json[i]
- const project = hitRows[i].getElementsByClassName(`project-name-column`)[0]
-
- const button = document.createElement(`button`)
- button.className = `btn btn-primary btn-sm`
- button.textContent = `Export`
- button.style.marginRight = `5px`
- project.prepend(button)
-
- if (hitExports === `all`) {
- button.dataset.toggle = `modal`
- button.dataset.target = `#hitExportModal`
- button.addEventListener(`click`, (event) => {
- event.target.closest(`.desktop-row`).click()
-
- for (const element of document.getElementsByClassName(`btn-hit-export`)) {
- element.dataset.hit = JSON.stringify(hit)
- }
- })
- } else {
- button.addEventListener(`click`, (event) => {
- event.target.closest(`.desktop-row`).click()
-
- if (hitExports === `short`) {
- short(event, hit)
- } else if (hitExports === `plain`) {
- plain(event, hit)
- } else if (hitExports === `bbcode`) {
- bbcode(event, hit)
- } else if (hitExports === `markdown`) {
- markdown(event, hit)
- }
- })
- }
- }
- }
- })()
-
- Object.assign(Number.prototype, {
- toMoneyString () {
- return `${this.toLocaleString(`en-US`, { minimumFractionDigits: 2 })}`
- },
- toTimeString () {
- let day
- let hour
- let minute
- let seconds = this
- minute = Math.floor(seconds / 60)
- seconds = seconds % 60
- hour = Math.floor(minute / 60)
- minute = minute % 60
- day = Math.floor(hour / 24)
- hour = hour % 24
-
- let string = ``
-
- if (day > 0) {
- string += `${day} day${day > 1 ? `s` : ``} `
- }
- if (hour > 0) {
- string += `${hour} hour${hour > 1 ? `s` : ``} `
- }
- if (minute > 0) {
- string += `${minute} day${minute > 1 ? `s` : ``}`
- }
- return string.trim()
- }
- })