IMDB List Importer

Import list of titles or people in the imdb list

目前为 2023-04-13 提交的版本。查看 最新版本

// ==UserScript==
// @name            IMDB List Importer
// @namespace       Neinei0k_imdb
// @include         https://www.imdb.com/list/*
// @version         8.1
// @license         GNU General Public License v3.0 or later
// @description	Import list of titles or people in the imdb list
// ==/UserScript==

let elements = createHTMLForm();

function log(level, message) {
	console.log("(IMDB List 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.isCSV = createCSVCheckbox(root);
    elements.isUnique = createUniqueCheckbox(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('.lister-search');
	if (container === null) {
		throw ".lister-search element not found";
	}
	let root = document.createElement('div');
	root.setAttribute('class', 'search-bar');
	root.style.height = 'initial';
  root.style.marginBottom = '30px';
	container.appendChild(root);

	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 createCSVCheckbox(root) {
	let isCSV = createCheckbox("Data from .csv file (otherwise extract ids from text)");
	isCSV.checkbox.checked = true;
	root.appendChild(isCSV.label);
	root.appendChild(document.createElement('br'));

	return isCSV.checkbox;
}

function createUniqueCheckbox(root) {
	let isUnique = createCheckbox("Add only unique elements");
	root.appendChild(isUnique.label);
	root.appendChild(document.createElement('br'));

	return isUnique.checkbox;
}

function createStatusBar(root) {
	let status = document.createElement('div');
	status.textContent = "Set-up parameters. Insert text or choose file. Press 'Import List' 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 List";
	root.appendChild(importList);

	importList.addEventListener('click', importListClickHandler, false);
}

function importListClickHandler(event) {
	if (elements.hasOwnProperty('isFromFile') && elements.isFromFile.checked) {
		readFile();
	} else {
		importList(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) {
		importList(extractItems(event.target.result));
	} else {
		log("Error", e.target.error);
		setStatus("Error: " + e.target.error);
	}
}

function extractItems(text) {
	try {
		let itemRegExp = getRegExpForItems();

		if (elements.isCSV.checked) {
			return extractItemsFromCSV(itemRegExp, text);
		} else {
			return extractItemsFromText(itemRegExp, text);
		}
	} catch (message) {
		log("Error", message);
		setStatus("Error: " + message);
		return [];
	}
}

function getRegExpForItems() {
	let listType;
	if (isPeopleList()) {
		log("Info", "List type: people");
		listType = "nm";
	} else if (isTitlesList()) {
		log("Info", "List type: titles");
		listType = "tt";
	} else {
		throw "Could not determine list type";
	}
	return listType + "[0-9]{7,8}";
}

function isPeopleList() {
	return document.querySelector('[data-type="People"]') !== null;
}

function isTitlesList() {
	return document.querySelector('[data-type="Titles"]') !== null;
}

function extractItemsFromCSV(re, text) {
	let table = parseCSV(text);
	let fields = findFieldNumbers(table);

  if (fields.description !== -1) {
		log("Info", "Found csv file fields Const(" + fields.const + ") and Description(" + fields.description + ")");
  } else {
    log("Info", "Found csv file field Const(" + fields.const + "). Description field is not found.");
  }

	re = new RegExp("^" + re + "$");
	let items = [];
	// Add elements to the list
	for (let i = 1; i < table.length; i++) {
		let row = table[i];
		if (re.exec(row[fields.const]) === null) {
			throw "Invalid 'const' field format on line " + (i+1);
		}
		if (elements.isUnique.checked) {
			let exists = items.findIndex(function(v){
				return v.const === row[fields.const];
			});
			if (exists !== -1) continue;
		}
		items.push({const: row[fields.const], description: (fields.description == -1 ? "" : row[fields.description])});
	}

	return items;
}

function parseCSV(text) {
	let lines = text.split('\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, 'description': -1};

	for (let i = 0; i < fieldNames.length; i++) {
		let fieldName = fieldNames[i].toLowerCase();
		if (fieldName === 'const') {
			fieldNumbers.const = i;
		} else if (fieldName === 'description') {
			fieldNumbers.description = i;
		}
	}

	if (fieldNumbers.const === -1) {
		throw "Field 'const' not found."
	}
	return fieldNumbers;
}

function extractItemsFromText(re, text) {
	re = new RegExp(re);
	let items = [];
	let e;
	while ((e = re.exec(text)) !== null) {
		let flag = '';
		if (elements.isUnique.checked)
			flag = 'g';
		text = text.replace(new RegExp(e[0], flag), '');
		items.push({const: e[0], description: ""});
	}
	return items;
}

function importList(list) {
	if (list.length === 0)
		return;

	let msg = "Elements to add: ";
	for (let i = 0;  i < list.length; i++)
		msg += list[i].const + ",";
	log("Info", msg);

	let l = {};
	l.list = list;
	l.ready = 0;
	l.list_id = /ls[0-9]{1,}/.exec(location.href)[0];
	l.hiddenElementData = getHiddenElementData(); // Data needs to be send with all requests.

	sendItem(l);
}

function getHiddenElementData() {
	let hiddenElement = document.querySelector('#main > input');
	if (hiddenElement === null) {
	 	log("Error", "Hidden element not found. It is required to be sent with every request.");
    setStatus("Error: Hidden element not found. It is required to be sent with every request.");
		return "";
	}
	return hiddenElement.id + "=" + hiddenElement.value;
}

function sendItem(l) {
	log("Info", 'Add element ' + l.ready + ': ' + l.list[l.ready].const);
	let url = 'https://www.imdb.com/list/' + l.list_id + '/' + l.list[l.ready].const + '/add';
	sendRequest(sendItemHandler, l, url, l.hiddenElementData);
}

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/x-www-form-urlencoded');
	x.send(data);
}

function sendItemHandler(l, event) {
  let target = event.target;
	log("Info", "Add element(" + l.list[l.ready].const + ") request: readyState(" + target.readyState + "), status(" + target.status + ")");
	if (target.readyState == 4 && target.status == 200) {
		let description = l.list[l.ready].description;
		if (description.length !== 0) {
			let listItemId = JSON.parse(target.responseText).list_item_id;
			let url = 'https://www.imdb.com/list/' + l.list_id + '/edit/itemdescription';
			let data = 'newDescription=' + description + '&listItem=' + listItemId + '&' + l.hiddenElementData
			sendRequest(sendItemDescriptionHandler, l, url, data);
		} else {
			showReady(l);
		}
	}
}

function sendItemDescriptionHandler(l, event) {
    let target = event.target;
	log("Info", "Add element(" + l.list[l.ready].const + ") description 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) {
		location.reload();
	} else {
		sendItem(l);
	}
}

QingJ © 2025

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