// ==UserScript== // @name incest mirror :) // @description kiss your sister // @version 1.0.0 // @include https://www.github.com/* // @include http://www.github.com/* // @include https://github.com/* // @include http://github.com/* // @grant GM.setValue // @grant GM.getValue // @grant GM.xmlHttpRequest // ==/UserScript== var mirrorIcon = ''; if (!GM) { alert("This script requires GreaseMonkey!"); return; } var config = {} async function api(url, data) { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "POST", url: `${config.instance}/api/v1/${url}`, data: JSON.stringify(data), headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": `token ${config.token}`, }, onload: (response) => { resolve(response); } }); }); } function createFooterElement() { // Create footer const footer = document.createElement('footer'); footer.className = 'SelectMenu-footer px-2'; // Create button const button = document.createElement('button'); button.type = 'button'; button.className = 'user-lists-menu-action btn-invisible btn btn-block text-left text-normal rounded-1 px-2'; // Create SVG const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('height', '16'); svg.setAttribute('viewBox', '0 0 16 16'); svg.setAttribute('version', '1.1'); svg.setAttribute('width', '16'); svg.setAttribute('data-view-component', 'true'); svg.setAttribute('class', 'octicon octicon-plus mr-1'); // Create path for SVG const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', 'M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z'); // Create text node const textNode = document.createTextNode('Mirror repository'); // Assemble the elements svg.appendChild(path); button.appendChild(svg); button.appendChild(textNode); footer.appendChild(button); return footer; } function createCheckbox(labelText, value) { const div = document.createElement('div'); div.className = 'form-checkbox mt-1 mb-0 p-1'; div.setAttribute('role', 'listitem'); const label = document.createElement('label'); label.className = 'd-flex'; const input = document.createElement('input'); input.type = 'checkbox'; input.checked = value; input.className = 'mx-0 js-user-list-menu-item'; const outerSpan = document.createElement('span'); outerSpan.setAttribute('data-view-component', 'true'); outerSpan.className = 'Truncate ml-2 text-normal f5'; const innerSpan = document.createElement('span'); innerSpan.setAttribute('data-view-component', 'true'); innerSpan.className = 'Truncate-text'; innerSpan.textContent = labelText; outerSpan.appendChild(innerSpan); label.appendChild(input); label.appendChild(outerSpan); div.appendChild(label); return div; } function createInput(placeholder, value) { const input = document.createElement('input'); input.className = 'FormControl-input'; input.placeholder = placeholder ?? ''; input.value = value ?? ''; return input; } function createSettingElement(key, [label, value, editable]) { if (typeof value === 'boolean') { const checkbox = createCheckbox(label, value); checkbox.querySelector('input').disabled = !editable; checkbox.querySelector('input').name = key; return checkbox; } else { const div = document.createElement('div'); div.className = "mx-0 mt-2"; const labelElement = document.createElement('label'); labelElement.textContent = label; div.appendChild(labelElement); const input = createInput(label, value); input.disabled = !editable; input.name = key; div.appendChild(input); return div; } } (async () => { config.instance = await GM.getValue("instance"); if (!config.instance) { var input = prompt("Enter your Gitea/Forgejo instance (include protocol, no trailing slash)"); GM.setValue("instance", input); config.instance = input; } config.token = await GM.getValue("token"); if (!config.token) { var input = prompt(`Enter your git access token\nSee: ${config.instance}/user/settings/applications`); GM.setValue("token", input); config.token = input; } function main() { var container = document.querySelector(".pagehead-actions"); if (!container) return; var mirrorButton = container.querySelector("#mirror"); if (mirrorButton != null) return; mirrorButton = container.children[container.children.length - 1].cloneNode(true); mirrorButton.id = "mirror"; var starCount = mirrorButton.querySelector("#repo-stars-counter-star"); if (starCount) starCount.parentNode.removeChild(starCount); var dropDown = mirrorButton.querySelector("details"); dropDown.querySelector("details-menu").removeAttribute("src"); var title = dropDown.querySelector(".SelectMenu-title"); title.textContent = "Mirror Settings"; var data = {} var address = document.querySelector("meta[name=go-import]").content.split(" ").slice(-1)[0]; var repo = address.split("/")[4].slice(0, -4); var settings = { "repo_owner": ["Owner", "mirrors", true], "repo_name": ["Repository Name", repo, true], "description": ["Description", "Mirror of " + address, true], "mirror": ["Mirror", true, true], "private": ["Private", false, true] } var list = dropDown.querySelector(".SelectMenu-list"); list.className += " flex-1 overflow-y-auto px-3 py-2 mb-0"; list.innerHTML = ""; Object.entries(settings).forEach(([key, value]) => { const settingElement = list.appendChild(createSettingElement(key, value)); settingElement.querySelector("input").addEventListener("input", (ev) => { var input = ev.target; data[input.name] = input.type == "checkbox" ? input.checked : input.value; }); }); var callback = async () => { mirrorButton.querySelector("button").disabled = true; footer.querySelector("button").disabled = true; var _data = { "clone_addr": address, "repo_name": repo, "mirror": true, "repo_owner": "mirrors", "description": "Mirror of " + address, "private": false, "issues": false, "pull_requests": false, ...data } var res = await api("repos/migrate", _data); try { var resp = JSON.parse(res); if (resp.message) { if (resp.message == "The repository with the same name already exists.") { window.open(`${config.instance}/mirrors/${repo}`); } else alert(resp.message); } else { window.open(resp.html_url); } } catch (err) { alert(res); } }; const footer = createFooterElement(repo); list.parentNode.appendChild(footer); list.parentNode.querySelector("footer button").onclick = callback; var btn = mirrorButton.querySelector(".unstarred button"); var newButton = btn.cloneNode(true); newButton.querySelector("svg").innerHTML = mirrorIcon; newButton.querySelector("span").innerText = "Mirror"; newButton.onclick = footer.querySelector("button").onclick; btn.parentNode.removeChild(btn); mirrorButton.innerHTML = ""; mirrorButton.appendChild(newButton); mirrorButton.appendChild(dropDown); container.prepend(mirrorButton); } setInterval(main, 1); })();