Skip to content

Instantly share code, notes, and snippets.

@xPomaHx
Created August 15, 2019 04:07
Show Gist options
  • Save xPomaHx/75d47e6ec86b0fb1d30a568781f200c1 to your computer and use it in GitHub Desktop.
Save xPomaHx/75d47e6ec86b0fb1d30a568781f200c1 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name tmmenedger
// @description qwe
// @namespace Habrahabr
// @version 1.1
// @include https://tmfeed.ru/popular/day/*
// @connect habr.com
// @require https://code.jquery.com/jquery-3.4.1.min.js
// require https://vuejs.org/js/vue.js
// @require https://vuejs.org/js/vue.min.js
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// ==/UserScript==
"use strict";
function broAjaxGet(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url,
onload: function ({ response }) {
resolve(response);
}
});
})
}
function broUniqueArByKey(ar, key) {
let tObj = {};
for (let el of ar) {
tObj[el[key]] = el;
}
let resultAr = []
for (let key2 in tObj) {
resultAr.push(tObj[key2])
}
return resultAr;
}
const tmfeedProvider = async url => {
const response = await broAjaxGet(url);
let posts = [];
posts.push(...JSON.parse(response).posts);
return {
nextUrl: null,
posts,
}
}
const habrProvider = async url => {
const response = await broAjaxGet(url);
let origin = (new URL(url)).origin
let href = $(".arrows-pagination__item-link_next", response).attr('href')
let newUrl = ""
if (href && href.indexOf("http") == -1) {
newUrl = origin + href;
}
else {
newUrl = href
}
let posts = [];
let $posts = $(".content-list > .content-list__item_post[id*='post']", response);
$posts.each((i, post) => {
let $title = $(post).find(".post__title_link")
let reading_count = Number($(post).find(".post-stats__views-count").text().replace(",", ".").replace("k", "e3"));
posts.push({
url: $title.attr("href"),
title: $title.text(),
reading_count,
})
});
return {
nextUrl: newUrl,
posts,
}
}
async function init() {
Vue.component('post', {
props: {
post: Object,
deleteBtn: {
type: Boolean,
default: false
},
},
computed: {
css() {
const { linePosition } = this.post;
return { background: `linear-gradient(90deg, #FFC0CB ${linePosition}%, #00FFFF ${linePosition}%` }
}
},
template: `
<li class=bro-post :style='css'>
<a :href='post.url' target=_blank class=tm-post__title-link>{{post.title}}</a>
<button v-if='deleteBtn' @click='$emit("removePost",post)'>X</button>
</li>
`,
});
Vue.component('posts', {
props: {
posts: Array,
title: String,
deleteBtn: {
type: Boolean,
default: false
},
},
template: `
<div>
<h3>{{title}}</h3>
<ul>
<transition-group name="list">
<post v-for='(post,index) in posts'
:key='post.url'
:post='post'
:deleteBtn='deleteBtn'
@removePost="$emit('removePost',$event)"
/>
</transition-group>
</ul>
</div>
`,
});
Vue.component('aggregator', {
props: {
url: String,
provider: Function,
},
data() {
return {
postsList: [],
nextUrl: "",
postsRaw: [],
viewedUrls: {},
maxNubmerPosts: 5,
httpLoading: false,
status: "making",
updatePostsCount: 0,
}
},
created() {
this.nextUrl = this.url;
if (localStorage.viewedUrls) {
let urls = JSON.parse(localStorage.viewedUrls);
this.viewedUrls = urls.reduce((el, next) => { el[next] = true; return el }, {});
}
this.updatePosts();
},
methods: {
handlerRemovePost(post) {
if (localStorage.viewedUrls) {
let urls = JSON.parse(localStorage.viewedUrls);
this.viewedUrls = urls.reduce((el, next) => { el[next] = true; return el }, {});
}
this.$set(this.viewedUrls, post.url, true)
localStorage.viewedUrls = JSON.stringify(Object.keys(this.viewedUrls));
this.updatePosts()
this.$emit('removePost', post)
},
async updatePosts() {
if (this.httpLoading) return
while (this.posts.length < this.maxNubmerPosts && this.nextUrl) {
this.httpLoading = true
this.status = "loading"
let { posts, nextUrl } = await this.provider(this.nextUrl);
this.updatePostsCount++
this.postsRaw.push(...posts);
this.nextUrl = nextUrl;
this.httpLoading = false
this.status = "ready"
}
if (!this.nextUrl) {
this.status = "done"
}
},
},
computed: {
posts() {
if (!this.postsRaw.length) return []
let posts = broUniqueArByKey(this.postsRaw, "url")
.filter(({ url }) => !this.viewedUrls[url])
.sort((a, b) => b.reading_count - a.reading_count);
if (!posts.length) return []
const maxCount = posts[0].reading_count;
const minCount = posts[posts.length - 1].reading_count;
posts = posts.map(post => {
post.linePosition = (post.reading_count - minCount) * 100 / (maxCount - minCount);
return post;
})
return posts
},
title() {
let title = this.url
title = title
.replace("https://habr.com/ru/hub/", "")
.replace("https://habr.com/ru/", "")
.replace("https://tmfeed.ru/api/v1/", "")
.replace(".json", "")
.replace("/page1", "")
return title + ": " + this.status + "(" + this.updatePostsCount + ")"
}
},
template: `
<div>
<div :style='{visibility: httpLoading?"visible":"hidden"}' class="bar">{{posts.length}}/{{maxNubmerPosts}}</div>
<posts
@removePost="handlerRemovePost"
deleteBtn
:posts=posts
:title="title"
/>
</div>
`,
});
new Vue({
data() {
return {
removedPosts: [],
categories: [].concat([
"https://habr.com/ru/hub/javascript/top/alltime/page1",
"https://habr.com/ru/hub/nodejs/top/alltime/page1",
"https://habr.com/ru/hub/freelance/top/alltime/page1",
/*"https://habr.com/ru/hub/career/top/alltime/page1",
"https://habr.com/ru/hub/webdev/top/alltime/page1",
"https://habr.com/ru/top/alltime/page1",
"https://habr.com/ru/top/yearly/page1",
"https://habr.com/ru/top/monthly/page1",
"https://habr.com/ru/top/daily/page1",
"https://habr.com/ru/all/page1",
"https://habr.com/ru/news/page1",*/
].map(url => ({ url, provider: habrProvider })),
["https://tmfeed.ru/api/v1/habrahabr-geektimes_top_daily.json",
"https://tmfeed.ru/api/v1/habrahabr-geektimes_top_weekly.json",
"https://tmfeed.ru/api/v1/habrahabr-geektimes_top_alltime.json",
"https://tmfeed.ru/api/v1/habrahabr-geektimes_top_monthly.json"
].map(url => ({ url, provider: tmfeedProvider })))
}
},
el: '.tm-posts',
template:
`
<div>
<template v-for='(category in categories'>
<aggregator v-bind='category' @removePost="removedPosts.unshift($event)"/>
</template>
<posts
:posts=removedPosts
title="Удаленные посты"
/>
</div>
`,
});
GM_addStyle(`
.bro-post{
display:flex;
margin-bottom:5px;
justify-content: center;
align-items: center;
text-align: left;
}
.bro-post a{
display:block;
width: 100%
}
.bro-post button{
cursor:pointer;
margin-left:auto;
}
.bar {
color: black;
text-align: center;
text-shadow: 0 0 2px white;
width: 100%;
height: 20px;
border: 1px solid #2980b9;
border-radius: 3px;
background-image:
repeating-linear-gradient(
-45deg,
#2980b9,
#2980b9 11px,
#eee 10px,
#eee 20px /* determines size */
);
background-size: 28px 28px;
animation: move .5s linear infinite;
}
@keyframes move {
0% {
background-position: 0 0;
}
100% {
background-position: 28px 0;
}
}
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active{
transition: all 0.4s;
}
.list-leave-active {
transition: all 0.00s;
}
.list-enter, .list-leave-to /* .list-leave-active до версии 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
`)
}
! function (win) {
if (window != window.top) return
win.addEventListener("load", setTimeout.bind(null, init, 999), false);
}(typeof unsafeWindow == 'undefined' ? window : unsafeWindow);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment