Midora/extension/background.ts

554 lines
17 KiB
TypeScript
Raw Normal View History

2020-09-14 11:47:17 +02:00
var browser: Browser = browser || chrome;
2018-09-16 12:27:42 +02:00
2019-06-29 15:16:27 +02:00
const PENDING_SUBMISSIONS = ':PENDING_SUBMISSIONS'
const MIGRATION = ':MIGRATION'
2019-01-14 23:11:06 +01:00
2020-09-14 11:49:54 +02:00
const CURRENT_VERSION = 100025;
2019-05-12 16:09:05 +02:00
const badIdentifiersReasons: { [id: string]: BadIdentifierReason } = {};
const badIdentifiers: { [id: string]: true } = {};
2019-01-18 19:56:54 +01:00
// If a user labels one of these URLs, they're making a mistake. Ignore the label.
// This list includes:
// * Social networks that are not supported (SN)
2019-01-18 19:56:54 +01:00
// * System pages of supported social networks
// * Archival and link shortening sites. (AR)
2019-04-18 16:53:57 +02:00
// * Reddit bots.
const badIdentifiersArray = [
'archive.is=AR',
'archive.org=AR',
'ask.fm=SN',
2019-01-14 23:11:06 +01:00
'assets.tumblr.com',
2019-03-01 00:04:29 +01:00
'bing.com',
2019-01-14 23:11:06 +01:00
'bit.ly',
'blogspot.com',
2019-02-08 20:42:25 +01:00
'change.org',
2019-06-29 14:36:50 +02:00
'chrome.google.com',
'curiouscat.me=SN',
'deviantart.com=SN',
'discord-store.com=SN',
'discord.gg=SN',
'discordapp.com=SN',
2019-04-08 16:27:41 +02:00
'disqus.com',
2019-06-29 14:36:50 +02:00
'docs.google.com',
'drive.google.com',
2019-03-01 00:04:29 +01:00
'duckduckgo.com',
2019-04-08 16:27:41 +02:00
'en.wikipedia.org',
2019-02-18 23:16:03 +01:00
'en.wikiquote.org',
'etsy.com=SN',
2019-04-08 16:27:41 +02:00
'facebook.com',
2019-01-14 23:11:06 +01:00
'facebook.com/a',
'facebook.com/ad_campaign',
'facebook.com/ads',
2019-07-10 20:36:34 +02:00
'facebook.com/advertising',
2019-01-14 23:11:06 +01:00
'facebook.com/ajax',
'facebook.com/bookmarks',
2019-07-10 20:36:34 +02:00
'facebook.com/browse',
2019-01-14 23:11:06 +01:00
'facebook.com/buddylist.php',
'facebook.com/bugnub',
2019-07-10 20:36:34 +02:00
'facebook.com/business',
2019-01-14 23:11:06 +01:00
'facebook.com/comment',
'facebook.com/composer',
2019-07-10 20:36:34 +02:00
'facebook.com/connect',
2019-06-29 14:36:50 +02:00
'facebook.com/docs',
2019-04-30 21:46:56 +02:00
'facebook.com/donate',
2019-01-14 23:11:06 +01:00
'facebook.com/events',
'facebook.com/findfriends',
'facebook.com/friends',
'facebook.com/fundraisers',
'facebook.com/games',
'facebook.com/groups',
2019-02-25 23:44:18 +01:00
'facebook.com/hashtag',
2019-01-14 23:11:06 +01:00
'facebook.com/help',
'facebook.com/home.php',
2019-10-06 10:30:29 +02:00
'facebook.com/instantgames',
2019-01-14 23:11:06 +01:00
'facebook.com/intl',
'facebook.com/jobs',
'facebook.com/l.php',
'facebook.com/language.php',
'facebook.com/legal',
'facebook.com/like.php',
'facebook.com/local_surface',
'facebook.com/logout.php',
2020-04-13 09:32:53 +02:00
'facebook.com/marketplace',
2019-01-14 23:11:06 +01:00
'facebook.com/mbasic',
2020-04-13 09:32:53 +02:00
'facebook.com/me',
2019-04-30 21:55:40 +02:00
'facebook.com/media',
2019-01-14 23:11:06 +01:00
'facebook.com/menu',
'facebook.com/messages',
'facebook.com/nfx',
'facebook.com/notes',
'facebook.com/notifications.php',
'facebook.com/notifications',
'facebook.com/nt',
'facebook.com/page',
'facebook.com/pages',
'facebook.com/people',
'facebook.com/permalink.php',
'facebook.com/pg',
'facebook.com/photo.php',
2020-04-13 09:32:53 +02:00
'facebook.com/photo',
2019-08-30 22:28:45 +02:00
'facebook.com/places',
2019-01-14 23:11:06 +01:00
'facebook.com/policies',
'facebook.com/privacy',
'facebook.com/profile.php',
2019-07-10 20:36:34 +02:00
'facebook.com/profile',
'facebook.com/public',
2019-01-14 23:11:06 +01:00
'facebook.com/rapid_report',
'facebook.com/reactions',
'facebook.com/salegroups',
'facebook.com/search',
'facebook.com/settings',
2019-02-25 23:44:18 +01:00
'facebook.com/sharer.php',
2019-01-14 23:11:06 +01:00
'facebook.com/shares',
2019-07-10 20:36:34 +02:00
'facebook.com/stories',
2019-01-14 23:11:06 +01:00
'facebook.com/story.php',
'facebook.com/ufi',
2019-07-10 20:36:34 +02:00
'facebook.com/watch',
'flickr.com=SN',
2019-08-30 22:28:45 +02:00
'goo.gl',
2019-01-14 23:14:36 +01:00
'google.com',
'googleusercontent.com',
2019-02-18 21:27:01 +01:00
'i.imgur.com',
'i.reddituploads.com',
'imdb.com=SN',
2019-02-18 21:27:01 +01:00
'imgur.com',
'instagram.com=SN',
'itunes.apple.com=SN',
'ko-fi.com=SN',
'linkedin.com=SN',
2019-01-18 19:56:54 +01:00
'mail.google.com',
2019-01-14 23:11:06 +01:00
'media.tumblr.com',
'medium.com',
2019-06-29 14:36:50 +02:00
'news.google.com',
'patreon.com=SN',
'paypal.com=SN',
'paypal.me=SN',
2019-02-18 21:27:01 +01:00
'play.google.com',
2019-01-18 19:56:54 +01:00
'plus.google.com',
2019-04-08 16:27:41 +02:00
'rationalwiki.org',
2019-01-14 23:11:06 +01:00
'reddit.com',
'reddit.com/r/all',
'reddit.com/r/popular',
'reddit.com/user/_youtubot_',
'reddit.com/user/animalfactsbot',
'reddit.com/user/anti-gif-bot',
'reddit.com/user/areyoudeaf',
2019-04-30 21:46:56 +02:00
'reddit.com/user/auto-xkcd37',
'reddit.com/user/automoderator',
'reddit.com/user/autotldr',
'reddit.com/user/biglebowskibot',
'reddit.com/user/bots_rise_up',
'reddit.com/user/cheer_up_bot',
'reddit.com/user/cheer-bot',
'reddit.com/user/clickablelinkbot',
'reddit.com/user/colorizethis',
'reddit.com/user/darnit_bot',
'reddit.com/user/darthplagueisbot',
'reddit.com/user/deepfrybot',
'reddit.com/user/dreamprocessor',
'reddit.com/user/drunkanimalfactbot',
'reddit.com/user/election_info_bot',
'reddit.com/user/eyebleachbot',
'reddit.com/user/factorial-bot',
'reddit.com/user/friendly-bot',
'reddit.com/user/garlicbot',
'reddit.com/user/gfycat_details_fixer',
'reddit.com/user/gifv-bot',
'reddit.com/user/good_good_gb_bb',
'reddit.com/user/goodbot_badbot',
'reddit.com/user/goodmod_badmod',
'reddit.com/user/gyazo_bot',
'reddit.com/user/haiku-detector',
2019-04-30 21:46:56 +02:00
'reddit.com/user/haikubot-1911',
'reddit.com/user/helperbot_',
'reddit.com/user/hug-bot',
'reddit.com/user/i_am_a_haiku_bot',
'reddit.com/user/ilinknsfwsubreddits',
'reddit.com/user/image_linker_bot',
'reddit.com/user/imdb_preview',
'reddit.com/user/imguralbumbot',
'reddit.com/user/jacksfilmsbot',
'reddit.com/user/jiffierbot',
'reddit.com/user/livetwitchclips',
'reddit.com/user/lyrics-matcher-bot',
'reddit.com/user/mailmygovnnbot',
'reddit.com/user/massdropbot',
'reddit.com/user/mentioned_videos',
'reddit.com/user/metric_units',
'reddit.com/user/mlbvideoconverterbot',
'reddit.com/user/morejpeg_auto',
'reddit.com/user/movieguide',
'reddit.com/user/multiusebot',
'reddit.com/user/news-summary',
'reddit.com/user/nflvideoconverterbot',
'reddit.com/user/octopusfunfacts',
'reddit.com/user/octupusfunfacts',
'reddit.com/user/opfeels',
'reddit.com/user/payrespects-bot',
'reddit.com/user/perrycohen',
'reddit.com/user/phonebatterylevelbot',
'reddit.com/user/picdescriptionbot',
'reddit.com/user/portmanteau-bot',
'reddit.com/user/quoteme-bot',
'reddit.com/user/redditsilverbot',
'reddit.com/user/redditstreamable',
'reddit.com/user/remindmebot',
'reddit.com/user/riskyclickerbot',
'reddit.com/user/rosey-the-bot',
'reddit.com/user/seriouslywhenishl3',
'reddit.com/user/shhbot',
'reddit.com/user/smallsubbot',
'reddit.com/user/snapshillbot',
'reddit.com/user/sneakpeekbot',
'reddit.com/user/stabbot_crop',
2019-04-30 21:46:56 +02:00
'reddit.com/user/stabbot',
'reddit.com/user/steamnewsbot',
'reddit.com/user/subjunctive__bot',
'reddit.com/user/table_it_bot',
'reddit.com/user/the-paranoid-android',
2019-04-30 21:46:56 +02:00
'reddit.com/user/thehelperdroid',
'reddit.com/user/thiscatmightcheeryou',
'reddit.com/user/timestamp_bot',
'reddit.com/user/timezone_bot',
'reddit.com/user/tiny_smile_bot',
'reddit.com/user/tipjarbot',
'reddit.com/user/tippr',
'reddit.com/user/totes_meta_bot',
'reddit.com/user/totesmessenger',
'reddit.com/user/tumblrdirect',
'reddit.com/user/tweetsincommentsbot',
'reddit.com/user/twitterlinkbot',
'reddit.com/user/twittertostreamable',
'reddit.com/user/video_descriptionbot',
'reddit.com/user/videodirectlinkbot',
'reddit.com/user/vredditmirrorbot',
'reddit.com/user/whodidthisbot',
'reddit.com/user/wikitextbot',
'reddit.com/user/xkcd_transcriber',
'reddit.com/user/youtubefactsbot',
2019-02-18 21:27:01 +01:00
'reddituploads.com',
2019-01-14 23:11:06 +01:00
'removeddit.com',
2019-06-29 14:36:50 +02:00
'sites.google.com',
'snapchat.com=SN',
'soundcloud.com=SN',
'steamcommunity.com=SN',
2019-01-14 23:11:06 +01:00
't.co',
't.umblr.com',
'tapatalk.com=SN',
2019-04-30 21:46:56 +02:00
'tmblr.co',
2019-01-14 23:11:06 +01:00
'tumblr.com',
'twitch.tv=SN',
2019-01-14 23:11:06 +01:00
'twitter.com',
'twitter.com/explore',
2019-01-14 23:11:06 +01:00
'twitter.com/hashtag',
'twitter.com/home',
2019-01-14 23:11:06 +01:00
'twitter.com/i',
'twitter.com/messages',
'twitter.com/notifications',
2019-01-14 23:11:06 +01:00
'twitter.com/search',
'twitter.com/settings',
2019-02-25 23:03:20 +01:00
'twitter.com/threader_app',
2019-01-14 23:11:06 +01:00
'twitter.com/threadreaderapp',
'twitter.com/who_to_follow',
'vimeo.com=SN',
'vk.com=SN',
2019-04-08 16:27:41 +02:00
'wikipedia.org',
2019-01-14 23:11:06 +01:00
'wordpress.com',
'www.tumblr.com',
'youtu.be',
'youtube.com',
2019-03-03 21:26:59 +01:00
'youtube.com/playlist',
2019-01-14 23:11:06 +01:00
'youtube.com/redirect',
'youtube.com/watch',
].map(x => {
const arr = x.split('=');
const id = arr[0];
if (arr[1]) badIdentifiersReasons[id] = <BadIdentifierReason>arr[1];
badIdentifiers[id] = true;
return id;
});
2019-01-14 23:11:06 +01:00
2019-06-29 19:54:25 +02:00
var lastSubmissionError: string = null;
2018-09-16 12:27:42 +02:00
2019-06-29 19:54:25 +02:00
var overrides: LabelMap = null;
2018-09-16 12:27:42 +02:00
var accepted = false;
2019-06-29 19:54:25 +02:00
var installationId: string = null;
2019-07-17 23:35:00 +02:00
var theme: string = '';
2019-01-14 23:11:06 +01:00
2019-07-17 23:35:00 +02:00
browser.storage.local.get(['overrides', 'accepted', 'installationId', 'theme'], v => {
if (!v.installationId) {
installationId = (Math.random() + '.' + Math.random() + '.' + Math.random()).replace(/\./g, '');
browser.storage.local.set({ installationId: installationId });
} else {
installationId = v.installationId;
}
2018-09-16 12:27:42 +02:00
accepted = v.accepted
overrides = v.overrides || {}
2019-07-17 23:35:00 +02:00
theme = v.theme;
2019-01-14 23:11:06 +01:00
const migration = overrides[MIGRATION] || 0;
2019-06-29 19:54:25 +02:00
if (migration < CURRENT_VERSION) {
2019-01-14 23:11:06 +01:00
2019-06-29 19:54:25 +02:00
for (const key of Object.getOwnPropertyNames(overrides)) {
if (key.startsWith(':')) continue;
2019-06-29 19:54:25 +02:00
if (key.startsWith('facebook.com/a.')) {
delete overrides[key];
continue;
}
2019-06-29 19:54:25 +02:00
if (key != key.toLowerCase()) {
let v = overrides[key];
2019-01-14 23:11:06 +01:00
delete overrides[key];
overrides[key.toLowerCase()] = v;
}
}
2019-06-29 19:54:25 +02:00
2019-01-14 23:11:06 +01:00
badIdentifiersArray.forEach(x => delete overrides[x]);
overrides[MIGRATION] = <any>CURRENT_VERSION;
2019-01-14 23:11:06 +01:00
browser.storage.local.set({ overrides: overrides });
}
2018-09-16 12:27:42 +02:00
})
2019-06-29 19:54:25 +02:00
const bloomFilters: BloomFilter[] = [];
2018-09-16 12:27:42 +02:00
async function loadBloomFilter(name: LabelKind) {
2018-09-16 12:27:42 +02:00
const url = browser.extension.getURL('data/' + name + '.dat');
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const array = new Uint32Array(arrayBuffer);
const b = new BloomFilter(array, 20);
b.name = name;
bloomFilters.push(b);
2018-09-16 12:27:42 +02:00
}
browser.runtime.onMessage.addListener<ShinigamiEyesMessage, ShinigamiEyesMessage | LabelMap>((message, sender, sendResponse) => {
2019-07-17 23:35:00 +02:00
if (message.setTheme) {
theme = message.setTheme;
browser.storage.local.set({ theme: message.setTheme });
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; ++i) {
try {
sendMessageToContent(tabs[i].id, null, { updateAllLabels: true });
2019-07-17 23:35:00 +02:00
} catch (e) { }
}
});
}
2018-12-16 17:36:19 +01:00
if (message.acceptClicked !== undefined) {
2018-09-16 12:27:42 +02:00
accepted = message.acceptClicked;
2018-12-16 17:36:19 +01:00
browser.storage.local.set({ accepted: accepted });
if (accepted && uncommittedResponse)
2018-09-16 12:27:42 +02:00
saveLabel(uncommittedResponse)
uncommittedResponse = null;
2019-07-17 23:35:00 +02:00
}
if (message.closeCallingTab) {
browser.tabs.remove(sender.tab.id);
2018-09-16 12:27:42 +02:00
return;
}
2019-06-29 19:54:25 +02:00
const response: LabelMap = {};
const transphobic = message.myself && bloomFilters.filter(x => x.name == 'transphobic')[0].test(message.myself);
for (const id of message.ids) {
2018-10-27 11:42:49 +02:00
if (overrides[id] !== undefined) {
response[id] = overrides[id];
continue;
}
if (transphobic) {
if (id == message.myself) continue;
let sum = 0;
for (let i = 0; i < id.length; i++) {
2018-09-16 12:27:42 +02:00
sum += id.charCodeAt(i);
}
2018-12-16 17:36:19 +01:00
if (sum % 8 != 0) continue;
2018-09-16 12:27:42 +02:00
}
for (const bloomFilter of bloomFilters) {
2018-10-27 11:42:49 +02:00
if (bloomFilter.test(id)) response[id] = bloomFilter.name;
2018-09-16 12:27:42 +02:00
}
}
2019-07-17 23:35:00 +02:00
response[':theme'] = <any>theme;
2018-09-16 12:27:42 +02:00
sendResponse(response);
});
loadBloomFilter('transphobic');
loadBloomFilter('t-friendly');
2019-06-29 15:16:27 +02:00
function createContextMenu(text: string, id: ContextMenuCommand) {
2018-09-16 12:27:42 +02:00
browser.contextMenus.create({
id: id,
title: text,
contexts: ["link"],
targetUrlPatterns: [
"*://*.facebook.com/*",
"*://*.youtube.com/*",
"*://*.reddit.com/*",
2018-11-16 21:51:31 +01:00
"*://*.twitter.com/*",
"*://*.t.co/*",
2018-11-16 21:51:31 +01:00
"*://medium.com/*",
2018-12-16 11:32:37 +01:00
"*://disqus.com/*",
2019-01-13 17:16:37 +01:00
"*://*.tumblr.com/*",
"*://*.wikipedia.org/*",
"*://*.rationalwiki.org/*",
"*://*.google.com/*",
2019-03-01 00:04:29 +01:00
"*://*.bing.com/*",
"*://duckduckgo.com/*",
2019-10-06 13:04:11 +02:00
"*://*/",
2019-10-26 12:38:08 +02:00
"*://*/?fbclid=*",
2019-10-06 13:04:11 +02:00
"*://*/about*",
"*://*/contact*",
"*://*/faq*",
"*://*/blog",
"*://*/blog/",
"*://*/news",
"*://*/news/",
"*://*/en/",
"*://*/index.html",
"*://*/index.php",
2018-09-16 12:27:42 +02:00
]
});
}
createContextMenu('Mark as anti-trans', 'mark-transphobic');
createContextMenu('Mark as t-friendly', 'mark-t-friendly');
createContextMenu('Clear', 'mark-none');
2019-07-17 23:35:00 +02:00
browser.contextMenus.create({ type: 'separator' });
createContextMenu('Settings', 'options');
2018-09-16 12:27:42 +02:00
createContextMenu('Help', 'help');
2019-06-29 19:54:25 +02:00
var uncommittedResponse: ShinigamiEyesSubmission = null;
2018-09-16 12:27:42 +02:00
async function submitPendingRatings() {
const submitted = getPendingSubmissions().map(x => x);
const requestBody = {
2018-12-16 17:36:19 +01:00
installationId: installationId,
lastError: lastSubmissionError,
2018-12-16 17:36:19 +01:00
entries: submitted
2018-10-27 17:02:15 +02:00
}
lastSubmissionError = null;
2018-10-27 17:02:15 +02:00
console.log('Sending request');
try {
const response = await fetch('https://k5kk18774h.execute-api.us-east-1.amazonaws.com/default/shinigamiEyesSubmission', {
body: JSON.stringify(requestBody),
method: 'POST',
credentials: 'omit',
});
if (response.status != 200) throw ('HTTP status: ' + response.status)
2019-06-29 19:54:25 +02:00
const result = await response.text();
if (result != 'SUCCESS') throw 'Bad response: ' + ('' + result).substring(0, 20);
overrides[PENDING_SUBMISSIONS] = <any>getPendingSubmissions().filter(x => submitted.indexOf(x) == -1);
browser.storage.local.set({ overrides: overrides });
2019-06-29 19:54:25 +02:00
} catch (e) {
lastSubmissionError = '' + e
}
2018-12-16 17:36:19 +01:00
2018-10-27 17:02:15 +02:00
}
2019-06-29 19:54:25 +02:00
function getPendingSubmissions(): ShinigamiEyesSubmission[] {
return <any>overrides[PENDING_SUBMISSIONS];
}
2018-10-27 17:02:15 +02:00
function saveLabel(response: ShinigamiEyesSubmission) {
2018-12-16 17:36:19 +01:00
if (accepted) {
if (!getPendingSubmissions()) {
overrides[PENDING_SUBMISSIONS] = <any>Object.getOwnPropertyNames(overrides)
.map<ShinigamiEyesSubmission>(x => { return { identifier: x, label: overrides[x] } });
2018-10-27 17:02:15 +02:00
}
2018-09-16 12:27:42 +02:00
overrides[response.identifier] = response.mark;
if (response.secondaryIdentifier)
overrides[response.secondaryIdentifier] = response.mark;
2018-12-16 17:36:19 +01:00
browser.storage.local.set({ overrides: overrides });
2019-05-12 16:10:36 +02:00
response.version = CURRENT_VERSION;
response.submissionId = (Math.random() + '').replace('.', '');
getPendingSubmissions().push(response);
2018-10-27 17:02:15 +02:00
submitPendingRatings();
2018-09-16 12:27:42 +02:00
//console.log(response);
sendMessageToContent(response.tabId, response.frameId, {
2019-10-06 14:29:26 +02:00
updateAllLabels: true,
confirmSetIdentifier: response.identifier,
confirmSetUrl: response.url,
confirmSetLabel: response.mark || 'none'
});
2018-09-16 12:27:42 +02:00
//browser.tabs.executeScript(response.tabId, {code: 'updateAllLabels()'});
return;
}
uncommittedResponse = response;
openHelp();
}
2018-12-16 17:36:19 +01:00
function openHelp() {
2018-09-16 12:27:42 +02:00
browser.tabs.create({
url: browser.extension.getURL('help.html')
})
}
2019-07-17 23:35:00 +02:00
function openOptions() {
browser.tabs.create({
url: browser.extension.getURL('options.html')
})
}
function sendMessageToContent(tabId: number, frameId: number, message: ShinigamiEyesCommand) {
const options = frameId === null ? undefined : { frameId: frameId };
console.log(message);
browser.tabs.sendMessage<ShinigamiEyesCommand, void>(tabId, message, options);
}
2018-09-16 12:27:42 +02:00
browser.contextMenus.onClicked.addListener(function (info, tab) {
2018-12-16 17:36:19 +01:00
if (info.menuItemId == 'help') {
2018-09-16 12:27:42 +02:00
openHelp();
return;
}
2019-07-17 23:35:00 +02:00
if (info.menuItemId == 'options') {
openOptions();
return;
}
2018-09-16 12:27:42 +02:00
const tabId = tab.id;
const frameId = info.frameId;
2019-06-14 19:03:50 +02:00
2019-06-29 15:41:37 +02:00
var label = <LabelKind>info.menuItemId.substring('mark-'.length);
2018-12-16 17:36:19 +01:00
if (label == 'none') label = '';
browser.tabs.sendMessage<ShinigamiEyesSubmission, ShinigamiEyesSubmission>(tabId, {
2018-09-16 12:27:42 +02:00
mark: label,
url: info.linkUrl,
2019-06-14 19:03:50 +02:00
tabId: tabId,
frameId: frameId,
// elementId: info.targetElementId,
debug: <any>overrides.debug
2019-06-14 19:03:50 +02:00
}, { frameId: frameId }, response => {
if (!response || !response.identifier) {
2019-10-06 14:29:26 +02:00
return;
}
2019-06-29 19:54:25 +02:00
if (response.mark) {
if (badIdentifiers[response.identifier]) {
sendMessageToContent(tabId, frameId, {
confirmSetIdentifier: response.identifier,
confirmSetUrl: response.url,
confirmSetLabel: 'bad-identifier',
badIdentifierReason: badIdentifiersReasons[response.identifier]
});
return;
}
if (response.secondaryIdentifier && badIdentifiers[response.secondaryIdentifier])
response.secondaryIdentifier = null;
}
2019-06-14 19:03:50 +02:00
response.tabId = tabId;
response.frameId = frameId;
2018-09-16 12:27:42 +02:00
saveLabel(response);
})
});