// ==UserScript==
// @name shoplifting-watcher
// @namespace nodelore.torn.shoplifting-watcher
// @version 0.3.3
// @description Watch shoplifting status for players.
// @author nodelore[2786679] Silmaril[2665762]
// @match https://www.torn.com/*
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function(){
'use strict';
// Avoid duplicated loading
if(window.SHOPLIFTING_WATCHER){
return;
}
window.SHOPLIFTING_WATCHER = true;
// ============================= Configuration ==============================
let API = localStorage.getItem("APIKey") || "";
const CONFIG_STORAGE_KEY = "SHOPLIFTING_WATCHER"
let watcher_config = {
interval: 60, // second,
enabled: [],
collapsed: false,
disabled: false,
offset: [30, 30],
both: [],
sort: [],
}
// ==========================================================================
let inPDA = false;
const PDAKey = "###PDA-APIKEY###";
if(PDAKey.charAt(0) !== "#"){
inPDA = true;
if(!API){
API = PDAKey;
}
}
if(localStorage.getItem(CONFIG_STORAGE_KEY)){
const config = JSON.parse(localStorage.getItem(CONFIG_STORAGE_KEY));
if(config["collapsed"] === undefined || config["sort"] === undefined || config["both"] === undefined){
update_config();
} else{
watcher_config = config;
}
}
if(API && !localStorage.getItem("APIKey")){
localStorage.setItem("APIKey", API);
}
// Ensure single instance, thanks to Silmaril :D
const scriptIsRunningKey = 'nodelore.torn.shoplifting-watcher.is-running';
const lastQueriedKey = "nodelore.torn.shoplifting-watcher.last-queried";
const currentTimestamp = parseInt(new Date().getTime() / 1000);
if(!localStorage.getItem(lastQueriedKey) || (currentTimestamp - parseInt(localStorage.getItem(lastQueriedKey)) > 5 * watcher_config.interval)){
console.log("[ShopliftingWatcher] Reset running flag to false");
localStorage.removeItem(scriptIsRunningKey);
}
if (localStorage.getItem(scriptIsRunningKey) === 'true') {
console.log("[ShopliftingWatcher] Script is already running in another tab.");
return;
}
console.log("[ShopliftingWatcher] Script started working.");
localStorage.setItem(scriptIsRunningKey, 'true');
window.addEventListener('beforeunload', () => {
localStorage.removeItem(scriptIsRunningKey);
});
window.addEventListener('unload', () => {
localStorage.removeItem(scriptIsRunningKey);
});
const update_config = ()=>{
localStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(watcher_config));
}
const notify = (notification)=>{
if(inPDA){
alert(notification);
return;
}
try{
if (Notification.permission === "granted") {
new Notification(notification);
}
else if (Notification.permission !== "denied") {
Notification.requestPermission().then(function (permission) {
if (permission === "granted") {
new Notification(notification);
}
});
}
} catch(e){
}
}
const addStyle = ()=>{
const styles = `
.dark-mode #shoplifting-body *{
color: #000;
}
.dragging{
opacity: .5;
}
.collapsed .shoplifting-status{
overflow: hidden;
height: auto;
}
.collapsed .shoplifting-item:not(.active){
display: none;
}
.collapsed .shoplifting-item-detail:not(.active){
display: none;
}
#shoplifting-body{
display: flex;
position: fixed;
width: 300px;
height: auto;
background: #FFF;
border-radius: 6px;
box-sizing: border-box;
padding: 10px 10px 0 10px;
flex-flow: column nowrap;
z-index: 1000000;
}
#shoplifting-body *{
user-select: none;
}
#shoplifting-body.hidden{
display: none !important;
}
.shoplifting-title{
display: flex;
flex-flow: row nowrap;
align-items: center;
margin-bottom: 10px;
}
.shoplifting-title div.heading{
font-size: 15px;
font-weight: bold;
cursor: grab;
}
.shoplifting-title div.close-btn{
margin-left: auto;
cursor: pointer !important;;
}
.shoplifting-status{
width: 100%;
display: flex;
height: 300px;
flex-flow: column nowrap;
overflow-y: scroll;
padding-right: 15px;
}
.shoplifting-status::-webkit-scrollbar{
width: 10px;
}
.shoplifting-status::-webkit-scrollbar-thumb {
border-radius: 10px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #a8bbbf;
background-image: -webkit-linear-gradient(
45deg,
rgba(255, 255, 255, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.2) 50%,
rgba(255, 255, 255, 0.2) 75%,
transparent 75%,
transparent
);
}
.shoplifting-status:-webkit-scrollbar-track {
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
border-radius: 10px;
background-color: #ededed;
}
.shoplifting-item{
width: 100%;
display: flex;
flex-flow: column nowrap;
border-top: 1px solid rgba(1, 1, 1, .1);
box-sizing: border-box;
padding: 5px 0;
position: relative;
}
.shoplifting-item-info{
display: flex;
flex-flow: column nowrap;
}
.shoplifting-item-name{
font-weight: bold;
height: 20px;
line-height: 20px;
display: flex;
align-items: center;
}
.shoplifting-item-name span{
position: relative;
font-size: 14px;
cursor: pointer;
text-indent: 24px;
transition: .3s all ease-in-out;
}
.shoplifting-item-name span:hover::after{
opacity: 1;
}
.stick-top .shoplifting-item-name span::after{
opacity: 1 !important;
}
.shoplifting-item-name span::after{
opacity: 0;
content: "\u21EE";
position: absolute;
left: -21px;
top: 50%;
transform: translateY(-50%);
font-size: 20px;
transition: .3s all ease-in-out;
}
.shoplifting-item-detail{
display: flex;
align-items: center;
margin-top: 3px;
}
.shoplifting-item-detail-name{
font-size: 13px;
}
.shoplifting-item-toggle{
display: flex;
margin-left: auto;
cursor: pointer;
height: 100%;
}
.shoplifting-item-toggle div{
width: 60px;
height: 20px;
line-height: 20px;
box-shadow: 0 0 6px 3px rgba(1, 1, 1, .1);
text-align: center;
font-weight: bold;
border-radius: 3px;
color: #FFF;
transition: .15s all ease-in-out;
opacity: .3;
}
.shoplifting-item-toggle div.active{
opacity: 1 !important;
}
.shoplifting-item-toggle div.active{
opacity: 1;
}
.shoplifting-item-toggle div:hover{
opacity: 1;
}
.shoplifting-item-toggle-on{
background: #82c91e;
}
.shoplifting-item-toggle-off{
background: #E54C19;
}
.shoplifting-apiusage{
display: flex;
align-items: center;
border-top: 1px solid rgba(1, 1, 1, .1);
box-sizing: border-box;
padding: 5px 0 5px 0;
}
.shoplifting-apiusage div{
font-weight: bold;
font-size: 14px;
}
.shoplifting-apiusage input{
color: rgba(1, 1, 1, .5);
height: 20px;
line-height: 20px;
margin-left: auto;
width: 120px;
background: #F2F2F2;
text-align: center;
font-weight: bold;
}
.toggle-btn{
position: absolute;
right: 30px;
}
.shoplifting-status .toggle-btn{
left: 150px;
}
.toggle-watcher{
display: none;
}
.toggle-watcher:checked + label{
background: #82c91e;
}
.toggle-watcher:checked + label:after{
left: calc(100% - 15px);
}
.toggle-watcher + label{
display: inline-block;
width: 40px;
height: 15px;
position: relative;
transition: 0.15s;
margin: 0px 20px;
box-sizing: border-box;
background: #ddd;
border-radius: 20px;
box-shadow: 1px 1px 3px #aaa;
}
.shoplifting-status .toggle-watcher + label:hover::before{
opacity: 1;
}
.toggle-watcher + label:after{
content: '';
display: block;
position: absolute;
left: 0px;
top: 0px;
width: 15px;
height: 15px;
transition: 0.15s;
cursor: pointer;
background: #fff;
border-radius: 50%;
box-shadow: 1px 1px 3px #aaa;
}
.shoplifting-status .toggle-watcher + label:before{
content: 'BOTH';
position: absolute;
right: -45px;
line-height: 21px;
height: 21px;
font-size: 12px;
color: #333;
transition: .3s all ease-in-out;
opacity: 0;
}
.shoplifting-collapsed{
padding: 5px 0 5px 0;
background: #F2F2F2;
text-align: center;
border-top: 1px solid rgba(1, 1, 1, .1);
font-weight: bold;
transition: .15s all ease-in-out;
margin-bottom: 8px;
}
.shoplifting-collapsed:hover{
background: #FFF;
cursor: pointer;
}
.both-active{
cursor: not-allowed !important;
}
.both-active div{
background: #ddd;
transition: none;
box-shadow: none;
color: #333;
opacity: 1;
}
.both-active div:hover{
opacity: 1;
}
`;
const isTampermonkeyEnabled = typeof unsafeWindow !== 'undefined';
if (isTampermonkeyEnabled){
GM_addStyle(styles);
} else {
let style = document.createElement("style");
style.type = "text/css";
style.innerHTML = styles;
document.head.appendChild(style);
}
}
addStyle();
let watcher_interval;
let icon_interval;
const insert_icon = ()=>{
if($("ul[class*=status-icons]").length === 0){
if(!icon_interval){
icon_interval = setInterval(()=>{
insert_icon();
icon_interval = null;
}, 500)
}
return;
}
if($("ul[class*=status-icons]").find(".shoplifting_watcher").length === 0){
const icon = $("<li class='shoplifting_watcher' title='Shoplifting Watcher'></li>");
icon.css({
"background-image": "url(/images/v2/editor/emoticons.svg)",
"cursor": "pointer",
"background-position": "-74px -42px"
});
icon.click(function(){
if($("div#shoplifting-body").hasClass("hidden")){
$("div#shoplifting-body").removeClass("hidden");
} else{
$("div#shoplifting-body").addClass("hidden");
}
})
$("ul[class*=status-icons]").prepend(icon);
}
}
const is_active = (status)=>{
for(let item of status){
if(item === 0){
return false;
}
}
return true;
}
const mock_status = (shop_status, single=true)=>{
for(let detail of shop_status){
detail["disabled"] = true;
if(single){
break;
}
}
}
const update_watcher = ()=>{
if(!API){
return;
}
let update_flag = false;
if($("#shoplifting-body .shoplifting-status div.shoplifting-item").length > 0){
update_flag = true;
}
fetch(`https://api.torn.com/torn/?selections=shoplifting&key=${API}`).then((res)=>{
if(res.ok){
res.json().then((data)=>{
let notification = "";
const shoplifting_data = data["shoplifting"];
localStorage.setItem(lastQueriedKey, parseInt(new Date().getTime() / 1000))
for(let shop_name in shoplifting_data){
const shop_status = shoplifting_data[shop_name];
const status_record = [];
if(!update_flag){
const shoplifting_item = $(`
<div class="shoplifting-item" data-shop="${shop_name}">
</div>
`);
const shoplifting_name = $(`
<div class="shoplifting-item-name">
<span>${shop_name}</span>
</div>
`);
shoplifting_name.find("span").click(function(){
const parent = $(this).parent().parent();
const shop_name = parent.attr("data-shop");
const shop_idx = watcher_config.sort.indexOf(shop_name);
if(shop_idx !== -1){
watcher_config.sort.splice(shop_idx, 1);
}
watcher_config.sort.unshift(shop_name);
if(watcher_config.sort.length > 3){
const pop_item = watcher_config.sort.pop();
const pop = $("#shoplifting-body").find(".shoplifting-status").find(`.shoplifting-item[data-shop=${pop_item}]`);
if(pop.length > 0){
pop.removeClass("stick-top");
}
}
update_config();
if(!parent.hasClass("stick-top")){
parent.addClass("stick-top");
}
$("#shoplifting-body").find(".shoplifting-status").prepend(parent);
});
const checked = watcher_config.both.indexOf(shop_name) === -1 ? "" : "checked";
const toggle_both = $(`<div class="toggle-btn">
<input type="checkbox" class="toggle-watcher" id="${shop_name}-watcher" ${checked}>
<label for="${shop_name}-watcher"></label>
</div>`);
toggle_both.find(`#${shop_name}-watcher`).change(function(){
const idx = watcher_config.both.indexOf(shop_name);
const parent = $(this).parent().parent().parent().find(".shoplifting-item-toggle");
if(idx !== -1){
const idx = watcher_config.both.indexOf(shop_name);
watcher_config.both.splice(idx, 1);
if(parent.hasClass("both-active")){
parent.removeClass("both-active");
}
} else{
watcher_config.both.push(shop_name);
if(!parent.hasClass("both-active")){
parent.addClass("both-active");
}
}
update_config();
});
shoplifting_name.append(toggle_both);
shoplifting_item.append(shoplifting_name);
let active_flag = false;
for(let detail of shop_status){
const detail_item = $(`<div class="shoplifting-item-detail"></div>`)
let prefix = ""
const key = `${shop_name}_${detail["title"].toLowerCase().replace(" ", "_")}`;
if(detail["disabled"]){
prefix = "❌";
status_record.push(1);
if(watcher_config.enabled.indexOf(key) !== -1 && watcher_config.both.indexOf(shop_name) === -1){
if(notification.length > 0){
notification += "\n";
}
notification += `【${shop_name}】【${detail["title"]}】 is disabled`;
}
} else{
prefix = "✅";
status_record.push(0);
}
detail_item.append($(`<div class="shoplifting-item-detail-name" data-key="${key}">${prefix} ${detail["title"]}</div>`));
let toggleOn = "";
let toggleOff = "";
if(watcher_config.enabled.indexOf(key) !== -1){
toggleOn = " active";
detail_item.addClass("active");
active_flag = true;
} else{
toggleOff = " active";
}
const toggle = $(`
<div class="shoplifting-item-toggle" data-key="${key}">
<div class="shoplifting-item-toggle-on${toggleOn}">ON</div>
<div class="shoplifting-item-toggle-off${toggleOff}">OFF</div>
</div>`
);
if(watcher_config.both.indexOf(shop_name) !== -1){
toggle.addClass("both-active")
}
toggle.click(function(){
const parent = $(this).parent();
const item = parent.parent();
const shop_name = item.attr("data-shop");
const checked = watcher_config.both.indexOf(shop_name) === -1 ? "" : "checked";
if(checked){
return;
}
const key = $(this).attr("data-key");
if(watcher_config.enabled.indexOf(key) !== -1){
const index = watcher_config.enabled.indexOf(key);
watcher_config.enabled.splice(index, 1);
$(`div.shoplifting-item-toggle[data-key=${key}] .shoplifting-item-toggle-off`).addClass("active");
$(`div.shoplifting-item-toggle[data-key=${key}] .shoplifting-item-toggle-on`).removeClass("active");
parent.removeClass("active");
let clear_flag = true;
item.find(".shoplifting-item-detail").each(function(){
if($(this).hasClass("active")){
clear_flag = false;
}
});
if(clear_flag){
item.removeClass("active");
}
} else{
watcher_config.enabled.push(key);
$(`div.shoplifting-item-toggle[data-key=${key}] .shoplifting-item-toggle-off`).removeClass("active");
$(`div.shoplifting-item-toggle[data-key=${key}] .shoplifting-item-toggle-on`).addClass("active");
if(!parent.hasClass("active")){
parent.addClass("active");
}
if(!item.hasClass("active")){
item.addClass("active");
}
}
update_config();
});
detail_item.append(toggle);
shoplifting_item.append(detail_item)
}
if(active_flag){
shoplifting_item.addClass("active");
}
if(watcher_config.sort.length === 0){
$("#shoplifting-body").find(".shoplifting-status").append(shoplifting_item);
} else{
let prev_item;
let found = false;
for(let i = 0; i < watcher_config.sort.length; i++){
if(watcher_config.sort[i] === shop_name){
const shop_idx = i - 1;
if(shop_idx >= 0){
prev_item = watcher_config.sort[shop_idx];
}
found = true;
break;
}
}
if(!found){
$("#shoplifting-body").find(".shoplifting-status").append(shoplifting_item);
} else{
shoplifting_item.addClass("stick-top");
if(prev_item && $("#shoplifting-body").find(".shoplifting-status").find(`.shoplifting-item[data-shop=${prev_item}]`).length > 0){
$("#shoplifting-body").find(".shoplifting-status").find(`.shoplifting-item[data-shop=${prev_item}]`).after(shoplifting_item)
} else{
$("#shoplifting-body").find(".shoplifting-status").prepend(shoplifting_item);
}
}
}
}
else{
for(let detail of shop_status){
let prefix = "";
const key = `${shop_name}_${detail["title"].toLowerCase().replace(" ", "_")}`;
if(detail["disabled"]){
prefix = "❌";
status_record.push(1);
if(watcher_config.enabled.indexOf(key) !== -1 && watcher_config.both.indexOf(shop_name) === -1){
if(notification.length > 0){
notification += "\n";
}
notification += `【${shop_name}】【${detail["title"]}】 is disabled`;
}
} else{
prefix = "✅";
status_record.push(0);
}
const target = $(`.shoplifting-item-detail-name[data-key="${key}"]`);
target.text(`${prefix} ${detail["title"]}`);
}
}
if(watcher_config.both.indexOf(shop_name) !== -1 && is_active(status_record)){
// forbidden notification if any of both is not disabled
if(notification.length > 0){
notification += "\n";
}
notification += `【${shop_name}】 ALL securities disabled`;
}
}
if(notification.length > 0){
notify(notification);
$(".shoplifting_watcher").attr("title", `Shoplifting Watcher\n${notification}`)
}
});
}
});
}
let is_dragging = false;
let offset = watcher_config.offset;
const insert_body = ()=>{
const collapsed_class = watcher_config.collapsed ? " collapsed" : "";
const watcher = $(`<div id="shoplifting-body" class="hidden${collapsed_class}">
<div class="shoplifting-status">
</div>
<div class="shoplifting-apiusage">
<div>Query interval: </div>
</div>
</div>`);
watcher.offset({
left: offset[0],
top: offset[1],
});
const input = $(`<input type="number" class="shoplifting-interval-input" step="1" value="${watcher_config.interval}"/>`);
input.keyup(function(){
const new_val = parseInt($(this).val());
if(new_val !== watcher_config.interval){
watcher_config.interval = new_val;
if(watcher_interval){
clearInterval(watcher_interval);
watcher_interval = setInterval(()=>{
update_watcher();
}, watcher_config.interval*1000)
}
update_config();
}
})
watcher.find(".shoplifting-apiusage").append(input);
let title_content = "Shoplifting watcher";
if(!API){
title_content = "No Public API"
}
const title = $(`
<div class="shoplifting-title">
</div>`
);
const heading = $(`<div class="heading">${title_content}</div>`);
heading.mousedown(function(e){
$("#shoplifting-body").addClass("dragging");
is_dragging = true;
offset = [
$("#shoplifting-body").offset().left - e.pageX,
$("#shoplifting-body").offset().top - e.pageY
]
});
heading.mousemove(function(e){
if(is_dragging){
$("#shoplifting-body").offset({
left: e.pageX + offset[0],
top: e.pageY + offset[1]
})
}
})
heading.mouseup(function(){
$("#shoplifting-body").removeClass("dragging");
is_dragging = false;
watcher_config.offset = [$("#shoplifting-body").offset().left, $("#shoplifting-body").offset().top];
update_config();
});
watcher.mouseleave(function(){
$("#shoplifting-body").removeClass("dragging");
is_dragging = false;
watcher_config.offset = [$("#shoplifting-body").offset().left, $("#shoplifting-body").offset().top];
update_config();
})
title.append(heading);
const checked = watcher_config.disabled ? "" : "checked";
const disabled_btn = $(`<div class="toggle-btn">
<input type="checkbox" class="toggle-watcher" id="toggle-watcher" ${checked}>
<label for="toggle-watcher"></label>
</div>`);
disabled_btn.find("#toggle-watcher").change(function(){
const status = this.checked;
if(status === true){
update_watcher();
if(!watcher_interval){
watcher_interval = setInterval(()=>{
update_watcher();
}, watcher_config.interval*1000);
if($("#shoplifting-body").hasClass("collapsed") && !watcher_config.collapsed){
$("#shoplifting-body").removeClass("collapsed");
}
}
} else{
if(watcher_interval){
clearInterval(watcher_interval);
watcher_interval = null;
$("div.shoplifting-item").each(function(){
$(this).remove();
});
if(!$("#shoplifting-body").hasClass("collapsed")){
$("#shoplifting-body").addClass("collapsed");
}
}
}
watcher_config.disabled = !status;
update_config();
});
title.append(disabled_btn);
const close_btn = $(`<div class='close-btn' title="Click to close">❌</div>`);
close_btn.click(function(){
$("div#shoplifting-body").addClass("hidden");
});
title.append(close_btn);
watcher.prepend(title);
const collapsed = $(`<div class="shoplifting-collapsed">${watcher_config.collapsed ? "Expand" : "Collapsed"}</div>`);
collapsed.click(function(){
if(watcher_config.collapsed === true){
watcher_config.collapsed = false;
$(this).text("Collapsed");
if($("#shoplifting-body").hasClass("collapsed")){
$("#shoplifting-body").removeClass("collapsed");
}
} else{
watcher_config.collapsed = true;
$(this).text("Expand");
if(!$("#shoplifting-body").hasClass("collapsed")){
$("#shoplifting-body").addClass("collapsed");
}
}
update_config();
})
watcher.append(collapsed);
$(body).append(watcher);
if(!watcher_config.disabled){
update_watcher();
if(!watcher_interval){
watcher_interval = setInterval(()=>{
update_watcher();
}, watcher_config.interval*1000);
}
}
insert_icon();
}
insert_body();
})();