MediaWiki:Gadget-Voting.js

/**
 * GAWAI PEMILIHAN / VOTING
 * 
 * ⚠️ Harap diperhatikan jika Anda mengubah skrip ⚠️
 * ini maka Anda akan memengaruhi seluruh pengguna
 * Wikipedia bahasa Indonesia
 * 
 * Untuk mengatur konfigurasi skrip ini 
 * (contoh halaman, periode, dan syarat minimal)
 * silahkan kunjungi [[MediaWiki:VotingConfig.json]]
 * 
 * Pemelihara: [[Pengguna:Janorovic Volkov]]
 */
// <nowiki>
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows']).then(function(){
	const api = new mw.Api();
	const page = mw.config.get("wgPageName");
	const user = mw.config.get("wgUserName");
	const action = mw.config.get("wgAction");
	if(action !== "view") return;
	if(!user) return;
	const windowManager = new OO.ui.WindowManager();
	$(document.body).append(windowManager.$element);
	
	async function loadConfig(){
		const res = await api.get({
			action:"query",
			prop:"revisions",
			titles:"MediaWiki:VotingConfig.json",
			rvprop:"content",
			rvslots:"main"
		});
		const obj = Object.values(res.query.pages)[0];
		return JSON.parse(obj.revisions[0].slots.main["*"]);
	}

	// =========================
	// [UPDATE] ambil config per halaman
	// =========================
	function getVoteConfig(cfg){
		return cfg.votes.find(v => v.page === page);
	}

	function isWithinPeriod(cfg){
		const now = new Date();
		const start = new Date(cfg.startDate+"T00:00:00Z");
		const end = new Date(cfg.endDate+"T23:59:59Z");
		return now>=start && now<=end;
	}

	async function validateUser(cfg){
		const res = await api.get({
			action:"query",
			meta:"userinfo",
			uiprop:"editcount|registrationdate"
		});
		const info = res.query.userinfo;
		let ageDays = 9999;
		if(info.registrationdate){
			const reg = new Date(info.registrationdate);
			ageDays = (new Date()-reg)/86400000;
		}
		return (
			info.editcount >= cfg.userContribMin &&
			ageDays >= cfg.userAgeMinDays
		);
	}

	async function findSection(sectionName){
		const res = await api.get({
			action:"parse",
			page:page,
			prop:"sections"
		});
		const sections = res.parse.sections;
		for(const s of sections){
			if(s.line.toLowerCase() === sectionName.toLowerCase()){
				return s.index;
			}
		}
		return null;
	}

	async function alreadyVoted(){
		const sectionIndex = await findSection("Pemungutan suara");
		if(sectionIndex === null) return false;
		const res = await api.get({
			action: "parse",
			page: page,
			prop: "wikitext",
			section: sectionIndex
		});
		const text = res.parse.wikitext["*"];
		const patterns = [
			"[[User:"+user,
			"[[Pengguna:"+user,
			"[[user:"+user,
			"[[pengguna:"+user
		];
		return patterns.some(p => text.includes(p));
	}
	
	function placeButtonAtSection(sectionId, wrap){
		const target = document.getElementById(sectionId);
		if(target){
			$(target).parent().before(wrap);
			return true;
		}
		return false;
	}
	
	function createButton(){
		const btn = new OO.ui.ButtonWidget({
			label:"🗳️ Berikan suara",
			flags:["primary","progressive"]
		});
		const wrap = $("<div>")
    		.attr("id","votingButton")
    		.css({
    			textAlign:"center",
    			margin:"20px 0"
    		})
    		.append(btn.$element);
    		// =========================
    		// [UPDATE] taruh di atas header "Pemungutan suara"
    		// =========================
    		if(!placeButtonAtSection("Pemungutan_suara", wrap)){
    			$(".mw-body-content").prepend(wrap);
    		}
    		btn.on("click",openDialog);
	}

	function openDialog(){
		function Dialog(config){
			Dialog.super.call(this,config);
		}
		OO.inheritClass(Dialog,OO.ui.ProcessDialog);
		Dialog.static.name = "voteDialog";
		Dialog.static.title = "Berikan suara";
		Dialog.static.size = "medium";
		Dialog.static.actions = [
			{action:"submit",label:"Kirim",flags:["primary","progressive"]},
			{action:"cancel",label:"Batal",flags:"safe"}
		];
		Dialog.prototype.initialize = function (){
			Dialog.super.prototype.initialize.call(this);
			this.vote = new OO.ui.RadioSelectWidget({
				items:[
					new OO.ui.RadioOptionWidget({data:"support",label:"Setuju"}),
					new OO.ui.RadioOptionWidget({data:"oppose",label:"Tidak setuju"}),
					new OO.ui.RadioOptionWidget({data:"abstain",label:"Abstain"})
				]
			});
			this.vote.selectItemByData("support");
			this.comment = new OO.ui.MultilineTextInputWidget({
				placeholder:"Komentar (opsional)",
				rows:3
			});
			this.panel = new OO.ui.PanelLayout({
				padded:true,
				expanded:false
			});
			this.panel.$element.append(
				this.vote.$element,
				$("<br>"),
				this.comment.$element
			);
			this.$body.append(this.panel.$element);
		};
		Dialog.prototype.getActionProcess = function(action){
			if(action==="cancel"){
				return new OO.ui.Process(()=>this.close());
			}
			if(action==="submit"){
				return new OO.ui.Process(async ()=>{ // [UPDATE] async biar bisa await
					const selected = this.vote.findSelectedItem();
					const vote = selected ? selected.getData() : null;
					const comment = this.comment.getValue();
					if(!vote){
						mw.notify("Pilih opsi terlebih dahulu");
						return;
					}
					// =========================
					// [UPDATE] cek ulang sebelum submit (anti race condition)
					// =========================
					if(await alreadyVoted()){
						mw.notify("⚠️ Anda sudah memberikan suara");
						return;
					}
					await submitVote(vote,comment); // [UPDATE] await
					this.close();
					$("#votingButton").html(
						$("<b>").text("🗳️ Anda sudah memberikan suara!")
					);
				});
			}
			return Dialog.super.prototype.getActionProcess.call(this,action);
		};
		const dialog = new Dialog();
		windowManager.addWindows([dialog]);
		windowManager.openWindow(dialog);
	}

	async function submitVote(type,comment){
		let template;
		let sectionName;
		if(type==="support"){
			template="{{Setuju}}";
			sectionName="Setuju";
		}
		if(type==="oppose"){
			template="{{Tidak setuju}}";
			sectionName="Tidak setuju";
		}
		if(type==="abstain"){
			template="{{Abstain}}";
			sectionName="Abstain";
		}
		const sectionIndex = await findSection(sectionName);
		if(sectionIndex===null){
			mw.notify("Header pemilihan tidak ditemukan");
			return;
		}
		let voteText="\n# "+template+". ";
		if(comment){
			voteText+=comment+" ";
		}
		voteText+="~~~~\n";
		await api.postWithToken("csrf",{
			action:"edit",
			title:page,
			section:sectionIndex,
			appendtext:voteText,
			summary:"Memberikan suara ("+sectionName+")",
			minor:true
		});
	}
	(async function(){
		const cfg = await loadConfig();
		// =========================
		// [UPDATE] ambil config khusus halaman ini
		// =========================
		const voteCfg = getVoteConfig(cfg);
		if(!voteCfg) return;
		// =========================
		// [UPDATE] cek periode per halaman
		// =========================
		if(!isWithinPeriod(voteCfg)){
			const now = new Date();
			const start = new Date(voteCfg.startDate);
			let text = "🗳️ Periode pemilihan telah berakhir";
			if(now < start){
				text = "🗳️ Pemungutan suara belum dimulai";
			}
			$(".mw-body-content").prepend(
				$("<div>")
					.css({textAlign:"center",margin:"20px",fontWeight:"bold"})
					.text(text)
			);
			return;
		}
		if(!(await validateUser(cfg))) return;
		if(await alreadyVoted()){
			$(".mw-body-content").prepend(
				$("<div>")
					.css({textAlign:"center",margin:"20px",fontWeight:"bold"})
					.text("🗳️ Anda sudah memberikan suara")
			);
			return;
		}
		createButton();
	})();
});
// </nowiki>

Konten ini disalin dari wikipedia, mohon digunakan dengan bijak.

×
Advertisement