IMDB Ratings Importer

Import ratings for movies, TV series and episodes from a csv file.

目前為 2023-05-17 提交的版本,檢視 最新版本

// ==UserScript==
// @name            IMDB Ratings Importer
// @namespace       Neinei0k_imdb
// @include         https://www.imdb.com/user/ur*/ratings*
// @version         1.01
// @license         GNU General Public License v3.0 or later
// @description	    Import ratings for movies, TV series and episodes from a csv file.
// ==/UserScript==

let elements = createHTMLForm();
 
function log(level, message) {
	console.log("(IMDB Ratings Importer) " + level + ": " + message);
}
 
function setStatus(message) {
	elements.status.textContent = message;
}
 
function createHTMLForm() {
	let elements = {};
 
  try {
    let root = createRoot();
    elements.text = createTextField(root);
 
    if (isFileAPISupported()) {
      elements.file = createFileInput(root);
      elements.isFromFile = createFromFileCheckbox(root);
    } else {
      createFileAPINotSupportedMessage(root);
    }
    elements.status = createStatusBar(root);
    
    createImportButton(root);
  } catch (message) {
   	log("Error", message);
  }
 
	return elements;
}
 
function isFileAPISupported() {
	return window.File && window.FileReader && window.FileList && window.Blob;
}
 
function createRoot() {
	let container = document.querySelector('div.article.listo');
  let nextChild = container.querySelector('div.lister');
	if (container === null) {
		throw "div.article.listo element not found";
	}
	let root = document.createElement('div');
	root.setAttribute('class', 'aux-content-widget-2');
	root.style.height = 'initial';
  root.style.marginTop = '30px';
  root.style.marginBottom = '30px';
	container.insertBefore(root, nextChild);
 
	return root;
}
 
function createTextField(root) {
	let text = document.createElement('textarea');
	text.style = "background-color: white; width: 100%; height: 100px; overflow: initial;";
	root.appendChild(text);
	root.appendChild(document.createElement('br'));
 
	return text;
}
 
function createFileInput(root) {
	let file = document.createElement('input');
	file.type = 'file';
	file.disabled = true;
  file.style.marginBottom = '10px';
	root.appendChild(file);
	root.appendChild(document.createElement('br'));
 
	return file;
}
 
function createFromFileCheckbox(root) {
	let isFromFile = createCheckbox("Import from file (otherwise import from text)");
	root.appendChild(isFromFile.label);
	root.appendChild(document.createElement('br'));
 
	isFromFile.checkbox.addEventListener('change', fromFileOrTextChangeHandler, false);
	
	return isFromFile.checkbox;
}
 
function createCheckbox(textContent) {
	let checkbox = document.createElement('input');
	checkbox.type = 'checkbox';
	checkbox.style = 'width: initial;';
 
	let text = document.createElement('span');
	text.style = 'font-weight: normal;';
	text.textContent = textContent;
 
	let label = document.createElement('label');
	label.appendChild(checkbox);
	label.appendChild(text);
 
	return {label: label, checkbox: checkbox};
}
 
function fromFileOrTextChangeHandler(event) {
	let isChecked = event.target.checked;
	elements.text.disabled = isChecked;
	elements.file.disabled = !isChecked;
}
 
function createFileAPINotSupportedMessage(root) {
	let notSupported = document.createElement('div');
	notSupported.style = 'font-weight: normal;';
  notSupported.style.marginTop = '10px';
  notSupported.style.marginBottom = '10px';
	notSupported.textContent = "Your browser does not support File API for reading local files.";
	root.appendChild(notSupported);
}
 
function createStatusBar(root) {
	let status = document.createElement('div');
	status.textContent = "Insert text or choose file. Press 'Import Ratings' button.";
  status.style.marginTop = '10px';
  status.style.marginBottom = '10px';
	root.appendChild(status);
 
	return status;
}
 
function createImportButton(root) {
	let importList = document.createElement('button');
	importList.class = 'btn';
	importList.textContent = "Import Ratings";
	root.appendChild(importList);
 
	importList.addEventListener('click', importRatingsClickHandler, false);
}
 
function importRatingsClickHandler(event) {
	if (elements.hasOwnProperty('isFromFile') && elements.isFromFile.checked) {
		readFile();
	} else {
		importRatings(extractItems(elements.text.value));
	}
}
 
function readFile() {
	let file = elements.file.files[0];
	if (file !== undefined) {
		log("Info", "Reading file " + file.name);
		setStatus("Reading file " + file.name);
		let fileReader = new FileReader();
		fileReader.onload = fileOnloadHandler;
		fileReader.readAsText(file);
	} else {
		setStatus("Error: File is not selected");
	}
}
 
function fileOnloadHandler(event) {
	if (event.target.error === null) {
		importRatings(extractItems(event.target.result));
	} else {
		log("Error", e.target.error);
		setStatus("Error: " + e.target.error);
	}
}
 
function extractItems(text) {
	try {
		return extractItemsFromCSV(text);
	} catch (message) {
		log("Error", message);
		setStatus("Error: " + message);
		return [];
	}
}
 
function extractItemsFromCSV(text) {
	let table = parseCSV(text);
	let fields = findFieldNumbers(table);
 
	log("Info", "Found csv file fields Const(" + fields.const + ") and Your Rating(" + fields.rating + ")");
 
	re = new RegExp("^tt[0-9]{7,8}$");
	let items = [];
	// Add elements to the list
	for (let i = 1; i < table.length; i++) {
    let fconst = table[i][fields.const];
    let frating = table[i][fields.rating];
		if (re.exec(fconst) === null) {
			throw "Invalid 'const' field format on line " + (i+1);
		}
    if (frating === "") {
      continue;
    }
    frating = parseInt(frating);
    if (isNaN(frating)) {
     	throw "Invalid 'your rating' field format on line " + (i+1); 
    }
		items.push({const: fconst, rating: frating});
	}
 
	return items;
}
 
function parseCSV(text) {
	let lines = text.split(/\r|\n/);
	let table = [];
	for (let i=0; i < lines.length; i++) {
		if (isEmpty(lines[i])) {
			continue;
		}
		let isInsideString = false;
		let row = [""];
		for (let j=0; j < lines[i].length; j++) {
			if (!isInsideString && lines[i][j] === ',') {
				row.push("");
			} else if (lines[i][j] === '"') {
				isInsideString = !isInsideString;
			} else {
				row[row.length-1] += lines[i][j];
			}
		}
		table.push(row);
		if (isInsideString) {
			throw "Wrong number of \" on line " + (i+1);
		}
		if (row.length != table[0].length) {
			throw "Wrong number of fields on line " + (i+1) + ". Expected " + table[0].length + " but found " + row.length + ".";
		}
	}
 
	return table;
}
 
function isEmpty(str) {
	return str.trim().length === 0;
}

function findFieldNumbers(table) {
	let fieldNames = table[0];
	let fieldNumbers = {'const': -1, 'rating': -1};
 
	for (let i = 0; i < fieldNames.length; i++) {
		let fieldName = fieldNames[i].toLowerCase().trim();
		if (fieldName === 'const') {
			fieldNumbers.const = i;
		} else if (fieldName === 'your rating') {
			fieldNumbers.rating = i;
		}
	}
 
	if (fieldNumbers.const === -1) {
		throw "Field 'const' not found.";
	} else if (fieldNumbers.rating === -1) {
   	throw "Field 'your rating' not found."; 
  }
	return fieldNumbers;
}
 
function importRatings(list) {
	if (list.length === 0)
		return;
 
	let l = {};
	l.list = list;
	l.ready = 0;
 
	sendItem(l);
}
 
function sendItem(l) {
	log("Info", 'Set rating ' + l.list[l.ready].rating + " for " + l.list[l.ready].const);
	let url = 'https://api.graphql.imdb.com/';
  let data = {"query": "mutation UpdateTitleRating($rating: Int!, $titleId: ID!) {\n  rateTitle(input: {rating: $rating, titleId: $titleId}) {\n    rating {\n      value\n      __typename\n    }\n    __typename\n  }\n}",
              "operationName": "UpdateTitleRating",
              "variables": {"rating": l.list[l.ready].rating,
                            "titleId": l.list[l.ready].const
                           }
  };
	sendRequest(sendItemHandler, l, url, JSON.stringify(data));
}
 
function sendRequest(handler, l, url, data) {
  var x = new XMLHttpRequest();
	x.onreadystatechange = function(event) {
			handler(l, event);
  }
	x.open('POST', url, true);
	x.setRequestHeader('Content-Type', 'application/json');
	x.send(data);
}
 
function sendItemHandler(l, event) {
  let target = event.target;
	log("Info", 'Set rating ' + l.list[l.ready].rating + " for " + l.list[l.ready].const + " request: readyState(" + target.readyState + "), status(" + target.status + ")");
	if (target.readyState == 4 && target.status == 200) {
		showReady(l);
	}
}
 
function showReady(l) {
	l.ready += 1;
	setStatus('Ready ' + l.ready + ' of ' + l.list.length + '.');
	if (l.ready == l.list.length) {
		setStatus("Finished");
	} else {
		sendItem(l);
	}
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址