скрипт
// ==UserScript==
// @name Yandex.Zen stats display
// @namespace https://zen.yandex.ru/
// @version 1.5
// @description Show extented statistics for Yandex.Zen posts
// @author Aleksey Derkach
// @match https://zen.yandex.ru/media/*
// @require https://yastatic.net/jquery/3.1.1/jquery.min.js
// @grant any
// @run-at document-idle
// ==/UserScript==
(function() {
// НАСТРОЙКИ
const moneyPerViews = 40; // цена 1000 просмотров статьи в рублях
const moneyPerViewsNarratives = 100; // цена 1000 просмотров нарратива в рублях
const disableOriginalStats = 1; // отключение родной статистики, 1 - да, 0 - нет
//
const rg = /\/.*-(.*)$/;
const baseUrl = "https://zen.yandex.ru/media-api/publisher-publications-stat";
const svgIcons = '<svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><symbol id="esi-newspaper" viewBox="0 0 24 24"><title>показы</title><path d="M21 6v-3h-21v16.5c0 0.828 0.672 1.5 1.5 1.5h20.25c1.243 0 2.25-1.007 2.25-2.25v-12.75h-3zM19.5 19.5h-18v-15h18v15zM3 7.5h15v1.5h-15zM12 10.5h6v1.5h-6zM12 13.5h6v1.5h-6zM12 16.5h4.5v1.5h-4.5zM3 10.5h7.5v7.5h-7.5z"></path></symbol><symbol id="esi-book" viewBox="0 0 24 24"><title>% дочитываний</title><path d="M21 3v19.5h-15.75c-1.243 0-2.25-1.007-2.25-2.25s1.007-2.25 2.25-2.25h14.25v-18h-15c-1.65 0-3 1.35-3 3v18c0 1.65 1.35 3 3 3h18v-21h-1.5z"></path><path d="M5.251 19.5v0c-0 0-0.001 0-0.001 0-0.414 0-0.75 0.336-0.75 0.75s0.336 0.75 0.75 0.75c0 0 0.001-0 0.001-0v0h14.247v-1.5h-14.247z"></path></symbol><symbol id="esi-coin-dollar" viewBox="0 0 24 24"><title>заработок</title><path d="M11.25 1.5c-6.213 0-11.25 5.037-11.25 11.25s5.037 11.25 11.25 11.25c6.213 0 11.25-5.037 11.25-11.25s-5.037-11.25-11.25-11.25zM11.25 21.75c-4.971 0-9-4.029-9-9s4.029-9 9-9c4.971 0 9 4.029 9 9s-4.029 9-9 9zM12 12v-3h3v-1.5h-3v-1.5h-1.5v1.5h-3v6h3v3h-3v1.5h3v1.5h1.5v-1.5h3l-0-6h-3zM10.5 12h-1.5v-3h1.5v3zM13.5 16.5h-1.5v-3h1.5v3z"></path></symbol><symbol id="esi-stopwatch" viewBox="0 0 24 24"><title>время чтения</title><path d="M12 4.528v-1.528h3v-1.5c0-0.828-0.672-1.5-1.5-1.5h-4.5c-0.828 0-1.5 0.672-1.5 1.5v1.5h3v1.528c-5.034 0.383-9 4.589-9 9.722 0 5.385 4.365 9.75 9.75 9.75s9.75-4.365 9.75-9.75c0-5.132-3.966-9.339-9-9.722zM16.553 19.553c-1.417 1.417-3.3 2.197-5.303 2.197s-3.887-0.78-5.303-2.197c-1.417-1.417-2.197-3.3-2.197-5.303s0.78-3.887 2.197-5.303c1.36-1.36 3.151-2.133 5.065-2.193l-0.508 7.36c-0.044 0.616 0.292 0.886 0.746 0.886s0.79-0.27 0.746-0.886l-0.508-7.36c1.914 0.059 3.705 0.832 5.065 2.193 1.417 1.417 2.197 3.3 2.197 5.303s-0.78 3.887-2.197 5.303z"></path></symbol><symbol id="esi-stats-dots" viewBox="0 0 24 24"><title>CTR</title><path d="M3 21h21v3h-24v-24h3zM6.75 19.5c-1.243 0-2.25-1.007-2.25-2.25s1.007-2.25 2.25-2.25c0.066 0 0.132 0.003 0.197 0.009l2.419-4.031c-0.231-0.353-0.365-0.775-0.365-1.228 0-1.243 1.007-2.25 2.25-2.25s2.25 1.007 2.25 2.25c0 0.453-0.135 0.875-0.365 1.228l2.419 4.031c0.065-0.006 0.13-0.009 0.197-0.009 0.050 0 0.1 0.002 0.149 0.005l3.993-6.987c-0.247-0.361-0.392-0.798-0.392-1.268 0-1.243 1.007-2.25 2.25-2.25s2.25 1.007 2.25 2.25c0 1.243-1.007 2.25-2.25 2.25-0.050 0-0.1-0.002-0.149-0.005l-3.993 6.987c0.247 0.361 0.392 0.798 0.392 1.268 0 1.243-1.007 2.25-2.25 2.25s-2.25-1.007-2.25-2.25c0-0.453 0.135-0.875 0.365-1.228l-2.419-4.031c-0.065 0.006-0.13 0.009-0.197 0.009s-0.132-0.003-0.197-0.009l-2.419 4.031c0.231 0.353 0.365 0.775 0.365 1.228 0 1.243-1.007 2.25-2.25 2.25z"></path></symbol><symbol id="esi-meter" viewBox="0 0 24 24"><title>карма</title><path d="M12 1.5c6.627 0 12 5.373 12 12 0 4.519-2.498 8.453-6.188 10.5h-11.625c-3.69-2.047-6.188-5.981-6.188-10.5 0-6.627 5.373-12 12-12zM18.795 20.295c1.815-1.815 2.815-4.228 2.815-6.795h-2.109v-1.5h1.993c-0.164-1.055-0.501-2.066-0.998-3h-2.495v-1.5h1.507c-0.221-0.276-0.458-0.541-0.712-0.795-1.083-1.083-2.38-1.876-3.795-2.339v1.634h-1.5v-1.993c-0.492-0.077-0.993-0.116-1.5-0.116s-1.008 0.040-1.5 0.116v1.993h-1.5v-1.634c-1.415 0.463-2.712 1.256-3.795 2.339-0.254 0.254-0.491 0.519-0.712 0.795h1.507v1.5h-2.496c-0.496 0.934-0.833 1.945-0.998 3h1.993v1.5h-2.109c0 2.567 1 4.98 2.815 6.795 0.251 0.251 0.514 0.486 0.787 0.705h4.508l0.857-12h1.286l0.857 12h4.508c0.273-0.219 0.536-0.454 0.787-0.705z"></path></symbol><symbol id="esi-flag" viewBox="0 0 24 24"><title>дочитывания</title><path d="M0 0h3v24h-3v-24z"></path><path d="M19.5 15.070c1.937 0 3.623-0.468 4.5-1.16v-12c-0.877 0.692-2.563 1.16-4.5 1.16s-3.623-0.468-4.5-1.16v12c0.877 0.692 2.563 1.16 4.5 1.16z"></path><path d="M14.25 0.762c-1.099-0.467-2.707-0.762-4.5-0.762-2.259 0-4.227 0.468-5.25 1.16v12c1.023-0.692 2.991-1.16 5.25-1.16 1.793 0 3.401 0.295 4.5 0.762v-12z"></path></symbol><symbol id="esi-eye" viewBox="0 0 24 24"><title>просмотры</title><path d="M12 4.5c-5.234 0-9.771 3.048-12 7.5 2.229 4.452 6.766 7.5 12 7.5s9.771-3.048 12-7.5c-2.229-4.452-6.766-7.5-12-7.5zM17.917 8.477c1.41 0.899 2.605 2.104 3.502 3.523-0.897 1.418-2.092 2.623-3.502 3.523-1.772 1.13-3.818 1.727-5.917 1.727s-4.145-0.597-5.917-1.727c-1.41-0.899-2.605-2.104-3.502-3.523 0.897-1.419 2.092-2.623 3.502-3.523 0.092-0.059 0.185-0.115 0.278-0.171-0.233 0.64-0.361 1.332-0.361 2.053 0 3.314 2.686 6 6 6s6-2.686 6-6c0-0.721-0.128-1.413-0.361-2.053 0.093 0.056 0.186 0.113 0.278 0.171v0zM12 9.75c0 1.243-1.007 2.25-2.25 2.25s-2.25-1.007-2.25-2.25 1.007-2.25 2.25-2.25 2.25 1.007 2.25 2.25z"></path></symbol><symbol id="esi-heart" viewBox="0 0 24 24"><title>% лайков</title><path d="M17.7 1.5c-2.523 0-4.694 2.052-5.699 4.195-1.006-2.143-3.178-4.195-5.7-4.195-3.478 0-6.3 2.823-6.3 6.301 0 7.075 7.137 8.93 12.001 15.924 4.597-6.951 11.999-9.075 11.999-15.924 0-3.478-2.823-6.301-6.3-6.301z"></path></symbol><symbol id="esi-arrow-up" viewBox="0 0 24 24"><title>лайки</title><path d="M12 0.75l-11.25 11.25h6.75v12h9v-12h6.75z"></path></symbol><symbol id="esi-arrow-down" viewBox="0 0 24 24"><title>дизлайки</title><path d="M12 23.25l11.25-11.25h-6.75v-12h-9v12h-6.75z"></path></symbol></defs></svg>';
const extraCss = '.esi {display: inline-block;width: 1em;height: 1em;stroke-width: 0;stroke: currentColor;fill: currentColor;margin-right:5px;font-size:15px;} .extstatitem {margin-right:10px; margin-top:5px; float:left; color:#5c6f7e;} .extstats{font-size:13px;padding-left:8px;display:inline-block;} #statscontrols{border:1px solid lightgrey; padding:0px 10px 10px 10px} .extstatsline{float:left;} .extstatsdelimeter{color: lightcyan;} .extarticles{display:inline-grid;} .extnarratives{display:inline-grid;} .totalextstats{display:inline-grid;}';
function getAllPostsLinks() {
return document.querySelectorAll('a.card__card-link');
}
function extractIdFromUrl(href) {
let matches = href.match(rg);
let id = "";
if(matches != null) {
id = href.match(rg)[1];
}
else {
return false;
}
return id;
}
function prepareIds() {
let ids = [];
const links = getAllPostsLinks();
for (let index = 0; index < links.length; index++) {
const item = links[index];
let href = $(item).attr('href');
let id = extractIdFromUrl(href);
if(id != false) {
ids.push(id);
}
else {
continue;
}
}
return ids;
}
function getPublicationById(publications, id) {
return publications.find(publication => publication.publicationId == id);
}
function getPublicationsOnPage(publications) {
let publicationsOnPage = [];
const links = getAllPostsLinks();
for (let index = 0; index < links.length; index++) {
const item = links[index];
let href = $(item).attr('href');
const matches = href.match(rg);
let id = "";
if(matches != null) {
id = href.match(rg)[1];
}
else {
continue;
}
let publication = getPublicationById(publications, id);
if (publication == undefined) {
continue;
}
const publicationTitle = $(item).find(".card__title").text();
publicationsOnPage.push(Object.assign({}, publication, {item, id, title: publicationTitle}));
}
return publicationsOnPage;
}
function formatFeedShows(feedShows) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-newspaper'></use></svg><span class='publication-card-item__stat-count'><strong>" + feedShows + "</strong></span></div>";
}
function formatShows(shows) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-eye'></use></svg><span class='publication-card-item__stat-count'><strong>" + shows + "</strong></span></div>";
}
function formatViewsTillEnd(viewsTillEnd) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-flag'></use></svg><span class='publication-card-item__stat-count'><strong>" + viewsTillEnd + "</strong></span></div>";
}
function formatLikes(likes) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-arrow-up'></use></svg><span class='publication-card-item__stat-count'><strong>" + likes + "</strong></span></div>";
}
function formatDislikes(dislikes) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-arrow-down'></use></svg><span class='publication-card-item__stat-count'><strong>" + dislikes + "</strong></span></div>";
}
function formatReadTime(readTime) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-stopwatch'></use></svg><span class='publication-card-item__stat-count'><strong>" + readTime + " мин.</strong></span></div>";
}
function formatCTR(ctr) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-stats-dots'></use></svg><span class='publication-card-item__stat-count'><strong>" + ctr + "%</strong></span></div>";
}
function formatReadRate(readRate) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-book'></use></svg><span class='publication-card-item__stat-count'><strong>" + readRate + "%</strong></span></div>";
}
function formatMoney(money) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-coin-dollar'></use></svg><span class='publication-card-item__stat-count'><strong>~" + money + " руб.</strong></span></div>";
}
function formatLikeRate(likeRate) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-heart'></use></svg><span class='publication-card-item__stat-count'><strong>" + likeRate + "%</strong></span></div>";
}
function formatLikeDifference(likeDifference) {
return "<div class='extstatitem'><svg class='esi'><use xlink:href='#esi-meter'></use></svg><span class='publication-card-item__stat-count'><strong>" + likeDifference + "</strong></span></div>";
}
function isNarrative(item) {
return $(item).parent().hasClass('card_narrative');
}
function displayStats(publications, forImport) {
const publicationsWithStats = getPublicationsOnPage(publications);
let totalFeedShows = 0;
let totalShows = 0;
let totalViewsTillEnd = 0;
let totalNarrativesViewsTillEnd = 0;
let totalPosts = 0;
let totalNarratives = 0;
let totalFeedShowsNarratives = 0;
let totalShowsNarratives = 0;
publicationsWithStats.forEach(publication => {
const {item} = publication;
const feedShows = publication.feedShows;
const likes = publication.likes;
const dislikes = 0;
const viewsTillEnd = publication.viewsTillEnd;
const sumViewTimeSec = publication.sumViewTimeSec;
const isNarrativeFlag = isNarrative(item);
let shows = 0;
let readRate = 0;
let readTime = 0;
let money = 0;
if (isNarrativeFlag) {
shows = publication.shows;
totalNarratives += 1;
money = ((shows / 1000) * moneyPerViewsNarratives).toFixed(2);
}
else {
shows = publication.views;
money = ((shows / 1000) * moneyPerViews).toFixed(2);
}
readRate = (viewsTillEnd / (shows / 100)).toFixed(2);
readTime = ((sumViewTimeSec/viewsTillEnd)/60).toFixed(2);
const ctr = (shows / (feedShows / 100)).toFixed(2);
const likeRate = (likes / (shows / 100)).toFixed(2);
const likesPerView = (shows / likes).toFixed(0);
const likeDifference = 0;
totalFeedShows += feedShows;
totalShows += shows;
totalViewsTillEnd += viewsTillEnd;
totalPosts += 1;
if (forImport == 1) {
const publicationTitle = ((publication.title).replace (/[\n\r]/g, ' ')).trim();
let importString = "";
if (isNarrativeFlag) {
totalFeedShowsNarratives += feedShows;
totalShowsNarratives += shows;
totalNarrativesViewsTillEnd += viewsTillEnd;
importString = publication.id + ";" + publicationTitle + ";" + feedShows + ";" + shows + ";" + viewsTillEnd + ";" + likes + ";" + dislikes + ";0;" + ctr + ";" + readRate + ";" + likeRate + ";" + likeDifference + ";1\r\n";
}
else {
importString = publication.id + ";" + publicationTitle + ";" + feedShows + ";" + shows + ";" + viewsTillEnd + ";" + likes + ";" + dislikes + ";" + ((sumViewTimeSec/viewsTillEnd)).toFixed(2) + ";" + ctr + ";" + readRate + ";" + likeRate + ";" + likeDifference + ";0\r\n";
}
$('#statsblock').val($('#statsblock').val() + importString);
}
else {
let statString = "";
$(item).parent().parent().parent().find(".publication-card-item__footer").find("div.extstats").remove();
const statOpen = "<div class='extstats'>";
if (isNarrativeFlag) {
totalFeedShowsNarratives += feedShows;
totalShowsNarratives += shows;
totalNarrativesViewsTillEnd += viewsTillEnd;
$(item).find("div.card__body").find('div.card__snippet').remove();
$(item).find("div.card__body").append('<div class="card__snippet clamped-text-4" style="max-height: 78.4px;">[нарратив]</div>');
}
const statsFirstLine = "<div class='extstatsfirst extstatsline'>" + formatFeedShows(feedShows) + formatShows(shows) + formatViewsTillEnd(viewsTillEnd) + formatLikes(likes) + "</div>";
const statsSecondLine = "<div class='extstatssecond extstatsline'>" + formatReadTime(readTime) + formatCTR(ctr) + formatReadRate(readRate) + "</div>";
const statsThirdLine = "<div class='extstatsthird extstatsline'>" + formatLikeRate(likeRate) + "<div class='extstatitem'><span class='publication-card-item__stat-count'>(<strong>1 лайк на " + likesPerView + " просмотров</strong>)</span></div>" + "</div>";
const statsFourhLine = "<div class='extstatsthird extstatsline'>" + formatMoney(money) + "</div>";
statString = statOpen + statsFirstLine + statsSecondLine + statsThirdLine + statsFourhLine + "</div>";
if (disableOriginalStats == 1) {
$(item).parent().parent().parent().find(".publication-card-item__footer").empty();
}
$(item).parent().parent().parent().find(".publication-card-item__footer").prepend(statString);
}
});
const totalPostsShows = totalShows - totalShowsNarratives;
const totalPostsViewsTillEnd = totalViewsTillEnd - totalNarrativesViewsTillEnd;
const totalPostFeedShows = totalFeedShows - totalFeedShowsNarratives;
const totalPostsMoney = (((totalPostsShows - 7000) / 1000) * moneyPerViews);
const totalNarrativesMoney = ((totalShowsNarratives / 1000) * moneyPerViewsNarratives);
const totalMoney = totalPostsMoney + totalNarrativesMoney;
const totalPostsCTR = (totalPostsShows / ( totalPostFeedShows / 100)).toFixed(2);
const totalNarrativesCTR = (totalShowsNarratives / ( totalFeedShowsNarratives / 100)).toFixed(2);
const totalPostsReadRate = (totalViewsTillEnd / (totalPostsShows / 100)).toFixed(2);
const totalNarrativesReadRate = (totalNarrativesViewsTillEnd / (totalShowsNarratives / 100)).toFixed(2);
const totalReadRate = (totalViewsTillEnd / (totalShows / 100)).toFixed(2);
const totalCTR = (totalShows / ( totalFeedShows / 100)).toFixed(2);
$("div#statscontrols").find("div.totalextstatscontainer").remove();
$("div#statscontrols").append("<div class='totalextstatscontainer'></div>");
$("div.totalextstatscontainer").append("<div class='totalextstats'></div>");
$("div.totalextstats").append("<div class='extstatsline'><span class='publication-card-item__stat-count'><u>Суммарные показатели</u></div>");
$("div.totalextstats").append("<div class='extstatsline'><div class='extstatitem'><span class='publication-card-item__stat-count'><strong>Публикаций: </strong>" + totalPosts + "</span></div></div>");
$("div.totalextstats").append("<div class='extstatsline'>" + formatFeedShows(totalFeedShows) + "</div>");
$("div.totalextstats").append("<div class='extstatsline'>" + formatShows(totalShows) + formatCTR(totalCTR) + "</div>");
$("div.totalextstats").append("<div class='extstatsline'>" + formatViewsTillEnd(totalViewsTillEnd) + formatReadRate(totalReadRate) + "</div>");
$("div.totalextstats").append("<div class='extstatsline'>" + formatMoney(totalMoney.toFixed(2)) + "</div>");
$("div.totalextstatscontainer").append("<hr class='extstatsdelimeter'>");
$("div.totalextstatscontainer").append("<div class='extstats extarticles'></div>");
$("div.extarticles").append("<div class='extstatsline'><span class='publication-card-item__stat-count'><u>Статей (" + (totalPosts - totalNarratives)+ "):</u></span></div>");
$("div.extarticles").append("<div class='extstatsline'>" + formatFeedShows(totalPostFeedShows) + "</div>");
$("div.extarticles").append("<div class='extstatsline'>" + formatShows(totalPostsShows) + formatCTR(totalPostsCTR) + "</div>");
$("div.extarticles").append("<div class='extstatsline'>" + formatViewsTillEnd(totalPostsViewsTillEnd) + formatReadRate(totalPostsReadRate) + "</div>");
$("div.extarticles").append("<div class='extstatsline'>" + formatMoney(totalPostsMoney.toFixed(2)) + "</div>");
if (totalNarratives > 0) {
$("div.totalextstatscontainer").append("<hr class='extstatsdelimeter'>");
$("div.totalextstatscontainer").append("<div class='extstats extnarratives'></div>");
$("div.extnarratives").append("<div class='extstatsline'><span class='publication-card-item__stat-count'><u>Нарративов (" + totalNarratives + "):</u></span></div>");
$("div.extnarratives").append("<div class='extstatsline'>" + formatFeedShows(totalFeedShowsNarratives) + "</div>");
$("div.extnarratives").append("<div class='extstatsline'>" + formatShows(totalShowsNarratives) + formatCTR(totalNarrativesCTR) + "</div>");
$("div.extnarratives").append("<div class='extstatsline'>" + formatViewsTillEnd(totalNarrativesViewsTillEnd) + formatReadRate(totalNarrativesReadRate) + "</div>");
$("div.extnarratives").append("<div class='extstatsline'>" + formatMoney(totalNarrativesMoney.toFixed(2)) + "</div>");
}
}
function loadPublications(ids) {
const url = baseUrl + '?publicationsIds=' + encodeURIComponent(ids.join(','));
return fetch(url, {credentials: 'same-origin'}).then(response => response.json());
}
function loadSinglePublicationStats(id) {
const url = singleUrl + '?publicationId=' + encodeURIComponent(id);
return fetch(url, {credentials: 'same-origin'}).then(response => response.json());
}
function chunked(arr, chunk = 5) {
let i, j;
let chunks = [];
for (i = 0, j = arr.length; i < j; i += chunk) {
chunks.push(arr.slice(i,i + chunk));
}
return chunks;
}
function prepareStats() {
const publicationsIds = prepareIds();
const chunks = chunked(publicationsIds, 50);
return Promise.all(chunks.map(loadPublications))
.then(responses => {
let publications = [];
responses.forEach(data => publications = [...publications, ...data.items]);
return publications;
});
}
$(document).ready(function() {
$("body").prepend('<style>' + extraCss + '</style>');
$("body").prepend(svgIcons);
$('div.header__right-menu').append('<div id="statscontrols"><p><strong>Расширенная статистика</strong></p></div>');
$('div#statscontrols').append('<a href="#" id="showstats" style="cursor: pointer;">Показать статистику</a><br/>');
$('div#statscontrols').append('<a href="#" id="showstatsblock" style="cursor: pointer;">Импорт статистики</a><br/>');
$('div#statscontrols').append('<textarea rows="40" cols="30" id="statsblock" name="statsblock" style="display: none;"></textarea>');
$('#showstatsblock').click(function() {
if ($('#showstatsblock').text() == 'Импорт статистики') {
$('#showstatsblock').text('Скрыть блок импорта');
$('#statsblock').val("");
const header = "Id;Статья;Показы;Просмотры;Дочитывания;Лайки;Дизлайки;Время чтения;CTR;Процент дочитываний;Процент лайков;Разница лайков;Нарратив\r\n";
$('#statsblock').val(header);
$('#statsblock').show();
prepareStats().then(publications => displayStats(publications, 1));
}
else {
$('#showstatsblock').text('Импорт статистики');
$('#statsblock').hide();
}
});
$('#showstats').click(function() {
if ($('#showstats').text() == 'Показать статистику') {
$('#showstats').text('Обновить статистику');
}
prepareStats().then(publications => displayStats(publications, 0));
});
});
})();