Skip to content

Instantly share code, notes, and snippets.

@AbrahamAriel
Last active August 8, 2025 03:33
Show Gist options
  • Select an option

  • Save AbrahamAriel/7a8d7cea1d8cbcd82700d67a09942a47 to your computer and use it in GitHub Desktop.

Select an option

Save AbrahamAriel/7a8d7cea1d8cbcd82700d67a09942a47 to your computer and use it in GitHub Desktop.
/**
* ChoiceScriptSavePluginInjector
*
* ---
*
* Licensed under the MIT License.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* ---
*
* ChoiceScriptSavePlugin by CJW (ChoiceScriptIDE):
* https://github.com/ChoicescriptIDE/ChoiceScriptSavePlugin,
* https://forum.choiceofgames.com/t/choicescript-saving-plugin-update-sept-2019/983
*
*/
// ==UserScript==
// @name ChoiceScriptSavePluginInjector
// @author AbrahamAriel
// @description Injects ChoicescriptIDE/ChoiceScriptSavePlugin into any CoG games. Includes extended functions such as exporting and importing saves.
// @version 1.9.1
// @license MIT
// @namespace https://gist.github.com/AbrahamAriel/7a8d7cea1d8cbcd82700d67a09942a47
// @downloadURL https://gist.githubusercontent.com/AbrahamAriel/7a8d7cea1d8cbcd82700d67a09942a47/raw/ChoiceScriptSavePluginInjector.user.js
// @updateURL https://gist.githubusercontent.com/AbrahamAriel/7a8d7cea1d8cbcd82700d67a09942a47/raw/ChoiceScriptSavePluginInjector.user.js
// @homepage https://www.reddit.com/r/choiceofgames/comments/ovo3eh/choicescriptsaveplugininjector_add_save_system_to/
// @match http://choiceofgames.com/*
// @match https://choiceofgames.com/*
// @match http://www.choiceofgames.com/*
// @match https://www.choiceofgames.com/*
// @match http://choiceofgames.com/user-contributed/*
// @match https://choiceofgames.com/user-contributed/*
// @match http://www.choiceofgames.com/user-contributed/*
// @match https://www.choiceofgames.com/user-contributed/*
// @exclude http://www.choiceofgames.com/category/*
// @exclude https://www.choiceofgames.com/category/*
// @exclude http://www.choiceofgames.com/profile/*
// @exclude https://www.choiceofgames.com/profile/*
// @exclude http://www.choiceofgames.com/blog/*
// @exclude https://www.choiceofgames.com/blog/*
// @exclude http://www.choiceofgames.com/api/*
// @exclude https://www.choiceofgames.com/api/*
// @exclude http://www.choiceofgames.com/about-us/*
// @exclude https://www.choiceofgames.com/about-us/*
// @exclude http://www.choiceofgames.com/contact-us/*
// @exclude https://www.choiceofgames.com/contact-us/*
// @exclude http://www.choiceofgames.com/privacy-policy/*
// @exclude https://www.choiceofgames.com/privacy-policy/*
// @exclude http://www.choiceofgames.com/looking-for-writers/*
// @exclude https://www.choiceofgames.com/looking-for-writers/*
// @exclude http://www.choiceofgames.com/make-your-own-games/*
// @exclude https://www.choiceofgames.com/make-your-own-games/*
// @exclude http://choiceofgames.com/category/*
// @exclude https://choiceofgames.com/category/*
// @exclude http://choiceofgames.com/profile/*
// @exclude https://choiceofgames.com/profile/*
// @exclude http://choiceofgames.com/blog/*
// @exclude https://choiceofgames.com/blog/*
// @exclude http://choiceofgames.com/api/*
// @exclude https://choiceofgames.com/api/*
// @exclude http://choiceofgames.com/about-us/*
// @exclude https://choiceofgames.com/about-us/*
// @exclude http://choiceofgames.com/contact-us/*
// @exclude https://choiceofgames.com/contact-us/*
// @exclude http://choiceofgames.com/privacy-policy/*
// @exclude https://choiceofgames.com/privacy-policy/*
// @exclude http://choiceofgames.com/looking-for-writers/*
// @exclude https://choiceofgames.com/looking-for-writers/*
// @exclude http://choiceofgames.com/make-your-own-games/*
// @exclude https://choiceofgames.com/make-your-own-games/*
// @run-at document-start
// @grant GM_getResourceText
// @resource ChoiceScriptSavePlugin https://cdn.jsdelivr.net/gh/AbrahamAriel/ChoiceScriptSavePlugin@latest/ChoiceScriptSavePlugin.js
// ==/UserScript==
(function() {
'use strict';
var savePluginScriptTag = document.createElement('script');
savePluginScriptTag.setAttribute('id', 'choice-script-save-plugin-injector');
const savePluginElement = Object.assign(
savePluginScriptTag, {
type: 'text/javascript',
innerHTML: GM_getResourceText('ChoiceScriptSavePlugin')
}
);
const navigatorScript = /(navigator\.js)/;
const mygame = /(mygame\.js)/;
const navigatorObserver = new MutationObserver((records) => {
records.forEach((mutation) => {
Array.prototype.forEach.call(mutation.addedNodes, (node) => {
if (node.tagName === "SCRIPT") {
if (navigatorScript.test(node.src)) {
try {
node.after(savePluginElement);
navigatorObserver.disconnect();
} catch (error) {
console.error('ChoiceScriptSavePluginInjector: Failed to inject save plugin!', error);
}
}
}
});
});
});
navigatorObserver.observe(document.head, {
subtree: true,
childList: true,
attributes: true,
attributesFilter: ['src'],
characterData: false,
});
document.addEventListener("DOMContentLoaded", (event) => {
/**
* Stops MutationObserver after 5 seconds.
* To prevent it from hogging unnecessary resources.
*/
setTimeout(() => {
console.debug('ChoiceScriptSavePluginInjector: Stopping MutationObserver... Plugin should be loaded by now.');
navigatorObserver.disconnect();
// -- Extended save functions --
const createExportAllSavesBtn = () => {
const parentElement = document.getElementById('menuButton').parentElement;
const choiceScriptSavePluginElement = document.getElementById('choice-script-save-plugin-injector');
const exportAllSavesAsJsonFile = () => {
ChoiceScriptSavePlugin._getSaveList(async (saveList) => {
if (!saveList) {
console.debug('ChoiceScriptSavePluginInjector: No saves found');
return;
}
var promises = {};
for (const saveId of saveList) {
promises[saveId] = new Promise((resolve, reject) => {
ChoiceScriptSavePlugin._getSaveData(saveId, (saveData) => {
if (!saveData) {
console.debug('ChoiceScriptSavePluginInjector: No save data found for save', saveId);
reject(`ChoiceScriptSavePluginInjector: No save data found for save ${saveId}`);
}
console.debug(`ChoiceScriptSavePluginInjector: Exporting save ${saveId}...`, saveData);
resolve(saveData);
});
});
}
const result = await Promise.allSettled(Object.values(promises));
if (result.length === 0) {
console.debug('ChoiceScriptSavePluginInjector: No saves found');
return;
}
console.debug(`ChoiceScriptSavePluginInjector: Promises resolved for ${result.length} saves...`, result);
for (const save of result) {
if (save.status === 'rejected') {
console.debug('ChoiceScriptSavePluginInjector: Failed to export save', save.reason);
}
}
const allSaves = result.filter((save) => save.status === 'fulfilled').map((save) => save.value);
console.debug(`ChoiceScriptSavePluginInjector: Exporting ${allSaves.length} saves...`, allSaves);
const saveName = 'all_saves';
const choiceTitle = (allSaves[0].stats.choice_title).replace(/\s+/g, '_').toLowerCase();
const json = JSON.stringify(allSaves);
console.debug('ChoiceScriptSavePluginInjector: Exporting all saves as JSON file...');
console.debug(`ChoiceScriptSavePluginInjector: Choice title: ${choiceTitle}`);
const blob = new Blob([json], { type: 'application/json' });
const link = document.createElement('a');
link.id = `${choiceTitle}-${saveName}-${Date.now()}`;
link.href = window.URL.createObjectURL(blob);
link.download = `${choiceTitle}-${saveName}-${Date.now()}.json`;
link.click();
window.URL.revokeObjectURL(link.href);
if (document.getElementById(link.id)) {
document.getElementById(link.id).remove();
}
console.debug('ChoiceScriptSavePluginInjector: All saves exported successfully!');
});
};
var btn = document.createElement('button');
btn.innerHTML = 'Export All';
btn.setAttribute('class', 'spacedLink savePluginBtn');
btn.addEventListener('click', () => {
if (choiceScriptSavePluginElement) {
exportAllSavesAsJsonFile();
}
});
parentElement.appendChild(btn);
};
const createExportSelectedSaveBtn = () => {
const parentElement = document.getElementById('menuButton').parentElement;
const choiceScriptSavePluginElement = document.getElementById('choice-script-save-plugin-injector');
const exportSelectedSaveAsJsonFile = () => {
const selected = document.getElementById('quickSaveMenu');
if (selected.value <= 0) {
console.debug('ChoiceScriptSavePluginInjector: No save selected');
return;
}
ChoiceScriptSavePlugin._getSaveData(selected.value, (saveData) => {
if (!saveData) {
console.debug('ChoiceScriptSavePluginInjector: No save data found');
return;
}
console.debug(`ChoiceScriptSavePluginInjector: Exporting save ${selected.value}...`, saveData);
const choiceTitle = (saveData.stats.choice_title).replace(/\s+/g, '_').toLowerCase();
const saveName = (saveData.stats._smSaveName).replace(/\s+/g, '_').toLowerCase();
const json = JSON.stringify(saveData);
console.debug('ChoiceScriptSavePluginInjector: Exporting selected save as JSON file...');
console.debug(`ChoiceScriptSavePluginInjector: Choice title: ${choiceTitle}`);
const blob = new Blob([json], { type: 'application/json' });
const link = document.createElement('a');
link.id = `${choiceTitle}-${saveName}-${Date.now()}`;
link.href = window.URL.createObjectURL(blob);
link.download = `${choiceTitle}-${saveName}-${Date.now()}.json`;
link.click();
window.URL.revokeObjectURL(link.href);
if (document.getElementById(link.id)) {
document.getElementById(link.id).remove();
}
console.debug('ChoiceScriptSavePluginInjector: Save exported successfully');
});
};
var btn = document.createElement('button');
btn.innerHTML = 'Export';
btn.setAttribute('class', 'spacedLink savePluginBtn');
btn.addEventListener('click', () => {
if (choiceScriptSavePluginElement) {
exportSelectedSaveAsJsonFile();
}
});
parentElement.appendChild(btn);
};
const createImportSavesBtn = async () => {
const parentElement = document.getElementById('menuButton').parentElement;
const choiceScriptSavePluginElement = document.getElementById('choice-script-save-plugin-injector');
const importSaves = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.multiple = false;
input.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = async (e) => {
const json = e.target.result;
var saves = JSON.parse(json);
if (!Array.isArray(saves)) {
saves = [saves];
}
console.debug('ChoiceScriptSavePluginInjector: Importing saves...', saves);
console.debug('ChoiceScriptSavePluginInjector: Number of saves to import:', saves.length);
var saveIds = [];
for (const save of saves) {
const saveId = save.stats._smSaveDateId;
ChoiceScriptSavePlugin._addToSaveList(saveId, (success) => {
if (!success) {
console.debug(`ChoiceScriptSavePluginInjector: Failed to add save ${saveId} to list.`);
return;
}
console.debug(`ChoiceScriptSavePluginInjector: Added save ${saveId} to list.`);
saveCookie(
function () {},
ChoiceScriptSavePlugin._formatSlotName(saveId), save.stats, save.temps, save.lineNum, save.indent, undefined, save.undeleted
);
console.debug(`ChoiceScriptSavePluginInjector: Imported save ${saveId} successfully.`);
saveIds.push(saveId);
});
await new Promise(resolve => setTimeout(resolve, 100));
}
console.debug('ChoiceScriptSavePluginInjector: Imported saves successfully.', saveIds);
var selectEle = document.getElementById("quickSaveMenu");
if (selectEle) {
selectEle.innerHTML = "";
ChoiceScriptSavePlugin._populateSaveMenu(selectEle);
}
};
reader.readAsText(file);
}
});
input.click();
};
var btn = document.createElement('button');
btn.innerHTML = 'Import';
btn.setAttribute('class', 'spacedLink savePluginBtn');
btn.addEventListener('click', () => {
if (choiceScriptSavePluginElement) {
importSaves();
}
});
parentElement.appendChild(btn);
};
// -- End of extended save functions --
createExportSelectedSaveBtn();
createExportAllSavesBtn();
createImportSavesBtn();
}, 5000);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment