// ==UserScript==
// @name 煎蛋吐槽管理器
// @namespace [email protected]
// @include http://jandan.net/duan*
// @include http://jandan.net/pic*
// @include http://jandan.net/ooxx*
// @include http://jandan.net/qa*
// @include http://jandan.net/pond*
// @include http://jandan.net/zhoubian*
// @include https://jandan.net/duan*
// @include https://jandan.net/pic*
// @include https://jandan.net/ooxx*
// @include https://jandan.net/qa*
// @include https://jandan.net/pond*
// @include https://jandan.net/zhoubian*
// @require https://cdn.bootcss.com/vue/2.4.2/vue.min.js
// @description save jandan.net's tucao
// @version 2.0.0
// @grant none
/*jshint esversion: 6 */
// ==/UserScript==
(function () {
if (window.top != window.self) return;
var Exceptions = {
BreakException: {},
FetchJsonDoneException: {},
}
/**
* 用于进行网络交互
* @class
*/
function Net() {
/**
* 使用get方法发送请求,默认接收json格式
* @param {string} url
* @param {object} params
* @param {string} type
* @return {Promise} 成功为返回数据
*/
var _get = function (url, params = {}, type = "json") {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
data: params,
type: "GET",
dataType: type,
success: data => resolve(data),
error: err => reject(err),
})
})
};
/**
* 异步方法同步处理,顺序获取一个post下面的所有吐槽
* @param {string} postId
* @return {Promise} 成功为吐槽列表
*/
var GetAllTucao = function (postId) {
var start = false;
function getFoo() {
return new Promise((resolve, reject) => {
var url = '/tucao/' + postId;
if (start !== false) url += '/n/' + start;
$.get(url, data => resolve(data))
})
}
const g = function* () {
try {
while (true) {
yield getFoo();
}
} catch (e) {
console.log(e);
}
};
function run(generator) {
return new Promise((resolve, reject) => {
const it = generator();
function go(action, tucao_list) {
action.value.then(function (tucao_data) {
if (action.done) {
resolve(tucao_list);
return;
}
tucao_list = tucao_list.concat(tucao_data.tucao);
if (!tucao_data.has_next_page) {
resolve(tucao_list);
} else {
start = tucao_list[tucao_list.length - 1].comment_ID;
return go(it.next(), tucao_list);
}
}, function (error) {
return go(it.throw(error));
});
}
go(it.next(), []);
});
}
return new Promise((resolve, reject) => {
run(g).then(data => {
var result = {};
data.forEach(element => {
result[element.comment_ID] = element;
});
resolve(result);
});
})
}
return {
GetAllTucao,
GetHtml: _get,
}
};
var net = Net();
/**
* 用于进行indexedDb的存储相关行为
* @class
*/
function Storage() {
var db = null,
//db名称
DB_NAME = 'jcdb',
//post表名称
POST_TABLE = 'jcpost',
//comment表名称
COMMENT_TABLE = 'jccomment';
var _this = this;
/**
* 初始化存储
* @return {Promise} 成功后直接回调
*/
var Init = function () {
return new Promise(function (resolve, reject) {
if (!'indexedDB' in window) {
reject('not support indexedDB');
return false;
}
var openRequest = indexedDB.open(DB_NAME, 2);
/**
* 数据库版本升级
* @param {Exception} e
*/
openRequest.onupgradeneeded = function (e) {
console.log("Upgrading...");
db = e.target.result;
if (!db.objectStoreNames.contains(DB_NAME)) {
try {
var store1 = db.createObjectStore(COMMENT_TABLE, {
keyPath: "tucaoId"
});
var store2 = db.createObjectStore(POST_TABLE, {
keyPath: "postId"
});
store1.createIndex("postId", "postId", {
unique: false
});
store1.createIndex("date", "date", {
unique: false
});
store2.createIndex("pageCate", "pageCate", {
unique: false
});
store2.createIndex("updateDate", "updateDate", {
unique: false
});
store2.createIndex("cate,date", ["pageCate", "updateDate"], {
unique: false
})
} catch (err) {
console.dir(err);
reject(err);
}
}
}
/**
* 数据库成功打开
* @param {Exception} e
* @return {Promise.resolve} 直接回调
*/
openRequest.onsuccess = function (e) {
console.log("Open success!");
db = e.target.result;
resolve();
}
/**
* 数据库打开错误
* @param {Exception} e
* @return {Promise.reject} 携带错误信息拒绝
*/
openRequest.onerror = function (e) {
console.log("Error");
console.dir(e);
reject(e);
}
})
};
/**
* 添加/更新评论
* @param {obj} comment 评论内容实体
* @param {bool} update_post 是否更新所在post的信息
*/
var AddComment = function (comment, update_post) {
return new Promise(function (resolve, reject) {
GetPostInfo(comment.postId).then(data => {
if (!update_post) return Promise.resolve();
_put(POST_TABLE, {
postId: comment.postId,
updateDate: comment.date,
pageCate: comment.page_cate,
pageNo: comment.page_no,
count: data && data.count ? data.count + 1 : 1,
})
}).then(() => {
delete comment.page_no;
_put(COMMENT_TABLE, comment)
}).then(() => {
resolve();
}).catch(err => {
reject(err);
})
})
}
/**
* 添加/更新post信息
* @param {obj} post
*/
var AddPost = function (post) {
return new Promise((resolve, reject) => {
_put(POST_TABLE, post).then(() => resolve()).catch(err => {
reject(err)
});
})
}
/**
* 根据索引和store名称,获取符合条件的指定条数内容
* @param {string} store_name store名称
* @param {string} idx 索引名称
* @param {string} key 索引条件
* @param {number} limit 获取条数
*/
var _get_all = function (store_name, idx, key, limit) {
return new Promise(function (resolve, reject) {
var t = db.transaction([store_name], 'readonly');
var store = t.objectStore(store_name);
var index = store.index(idx);
var range = IDBKeyRange.only(key);
var getAllRequest = index.getAll(range, limit);
getAllRequest.onsuccess = function () {
resolve(getAllRequest.result);
}
getAllRequest.onerror = function (err) {
reject(err);
}
})
}
/**
* 获取不同页面下的所有post,固定最大50条
* @param {string} cate 页面分类,如ooxx、pic、duan
*/
var GetPostsByCate = function (cate) {
return new Promise(function (resolve, reject) {
_get_all(POST_TABLE, 'pageCate', cate, 50).then(data => {
data.sort((a, b) => {
var dateA = new Date(a.updateDate);
var dateB = new Date(b.updateDate);
if (dateA < dateB) return 1;
if (dateA > dateB) return -1;
return 0;
})
resolve(data);
}).catch(err => {
reject(err)
});
})
};
/**
* 获取post下的所有吐槽,固定最大50条
* @param {string} postId
*/
var GetCommentsByPostId = function (postId) {
return new Promise(function (resolve, reject) {
_get_all(COMMENT_TABLE, 'postId', postId, 50).then(data => {
resolve(data);
}).catch(err => {
reject(err);
});
})
};
/**
* 获取store实体
* @param {string} store_name store名称
* @param {string} mode 打开模式
*/
var _get_store = function (store_name, mode = 'readonly') {
var t = db.transaction([store_name], mode);
var store = t.objectStore(store_name);
return store;
}
/**
* 根据postid获取post的信息
* @param {string} postId
*/
var GetPostInfo = function (postId) {
return new Promise(function (resolve, reject) {
_get(POST_TABLE, postId).then(data => {
resolve(data);
}).catch(err => {
reject(err);
})
});
}
/**
* 根据commentid获取吐槽的信息
* @param {string} commentId
* @return {Promise}
*/
var GetCommentInfo = function (commentId) {
return new Promise(function (resolve, reject) {
_get(COMMENT_TABLE, commentId).then(data => {
resolve(data);
}).catch(err => {
reject(err);
})
});
}
/**
* 从特定的store,获取指定id的实体
* @param {string} store_name
* @param {string} key
* @return {Promise}
*/
var _get = function (store_name, key) {
return new Promise(function (resolve, reject) {
var request = _get_store(store_name).get(key);
request.onsuccess = function () {
// console.log(request.result);
resolve(request.result);
}
request.onerror = function (err) {
console.dir(err);
reject(err);
}
})
}
/**
* 添加/更新内容到指定store
* @param {string} store_name
* @param {string} value
* @return {Promise}
*/
var _put = function (store_name, value) {
return new Promise(function (resolve, reject) {
var request = _get_store(store_name, 'readwrite').put(value);
request.onsuccess = function () {
console.log(request.result);
resolve(request.result);
}
request.onerror = function (err) {
console.dir(err);
reject(err);
}
})
}
/**
* 删除指定store下的指定条目
* @param {string} store_name
* @param {string} key
* @return {Promise}
*/
var _delete = function (store_name, key) {
return new Promise(function (resolve, reject) {
var request = _get_store(store_name, 'readwrite').delete(key);
request.onsuccess = function () {
resolve();
}
request.onerror = function (err) {
console.dir(err);
reject(err);
}
})
};
/**
* 删除post
* @param {string} postId
* @return {Promise}
*/
var DeletePost = function (postId) {
return new Promise((resolve, reject) => {
_delete(POST_TABLE, postId).then(() => {
resolve();
}).catch(err => {
reject(err);
})
});
};
/**
* 删除吐槽
* @param {string} commentId
*/
var DeleteComment = function (commentId) {
return new Promise(function (resolve, reject) {
GetCommentInfo(commentId).then(c => {
GetPostInfo(c.postId).then(p => {
_delete(COMMENT_TABLE, commentId).then(() => {
if (p.count <= 1) _delete(POST_TABLE, p.postId).catch(err => {
reject(err)
});
else {
p.count -= 1;
_put(POST_TABLE, p).catch(err => {
reject(err)
});
}
}).catch(err => {
reject(err);
});
});
});
});
};
return {
Init,
AddComment,
GetCommentsByPostId,
GetPostsByCate,
DeletePost,
AddPost,
GetPostInfo,
};
}
var storage = Storage();
$.jcsaver = {
//固定的页面
jc_pages: [{
id: 'ooxx',
name: '妹子图',
}, {
id: 'pic',
name: '无聊图',
},
{
id: 'duan',
name: '段子',
},
{
id: 'qa',
name: '问答',
},
{
id: 'pond',
name: '鱼塘',
},
{
id: 'zhoubian',
name: '周边',
},
],
st_index_prefix: "jc_index_",
st_index_key: "",
st_item_prefix: "jc_",
st_db: null,
page_cate: undefined,
//自定义css
jc_css: `
#jc_area {
text-align: left;
text-indent: 0;
width: 50%;
min-height: 300px;
background-color: #f2f2f2;
z-index: 100000;
position: fixed;
top: 10px;
left: 25%;
border: 1px solid;
list-style: none;
}
#jc_area > .jc_switch_bar span {
display: table-cell;
background-color: #bfbfbf;
cursor: pointer;
text-align: center;
line-height: 30px;
}
.jc_switch_bar span:hover {
background-color: #737373;
color: white;
}
.current_tab {
background: #636363 !important;
color: white;
}
#jc_area > .jc_switch_bar {
display: table;
width: 100%;
}
#jc_btn {
padding: 2px 10px;
height: 50px;
background-color: rgba(100, 100, 100, 0.6);
z-index: 100000;
text-align: center;
line-height: 50px;
position: fixed;
bottom: 10px;
right: 10px;
border-radius: 5px 5px 5px 5px;
}
#jc_btn > span {
cursor: pointer;
text-decoration: underline;
}
.jc_bar > span {
display: block;
float: right;
margin-right: 20px;
width: 20px;
text-align: center;
height: 15px;
margin-top: 5px;
line-height: 15px;
border-radius: 5px;
cursor: pointer;
color: white;
font-size: 10px;
box-shadow: 1px 1px 1px #6c6c6c;
}
.jc_bar > span.jc_go{
background-color: #008f52;
}
.jc_bar > span.jc_del{
background-color: #959595;
}
a.jc_exp{
cursor: pointer;
}
.jc_bar {
line-height: 30px;
}
.jc_bar0 {
background-color: #c9c9c9;
}
.jc_hint {
height: 24px;
width: 24px;
line-height: 24px;
text-align: center;
color: green;
cursor: pointer;
}
strong.jc_hint:before {
content: '[';
}
strong.jc_hint:after {
content: '条回复]';
}
.jc_sync {
position: absolute;
left: -70px;
-webkit-animation: sync-color 1.0s infinite ease-in-out;
animation: sync-color 1.0s infinite ease-in-out;
font-weight: 900;
font-size: 13px;
}
@-webkit-keyframes sync-color {
0% {
color: gray;
}
100% {
color: red;
}
}
@keyframes sync-color {
0% {
color: gray;
}
100% {
color: red;
}
}
`,
//自定义vue HTML
jc_html: $(`
<div id="jc_main">
<div id="jc_area" v-show="show">
<div class="jc_switch_bar"><span v-for="page in pages" v-bind:page="page.id" v-on:click="current_page = page.id; refresh();" v-bind:class="page.id==current_page?'current_tab jc_tab':'jc_tab'">{{ page.name }}</span></div>
<div class="jc_list_area">
<div v-for="(item,idx) in items" v-bind:class="'jc_bar jc_bar'+idx%2">●
<a v-bind:cno="item.k" v-on:click="loadComments(item.postId, item.visable)" class="jc_exp">第{{ item.pageNo }}页 第{{ item.postId }}楼 {{ item.count }}条评论<span class="reply-total" v-if="item.reply_total_count > 0">,{{ item.reply_total_count }}条回复<span/></a>
<span class="jc_go" title="Go to this location"><a v-bind:href="item|getUrl" target="blank" style="color:white;">✈</a></span>
</span>
<span class="jc_del" v-on:click="deletePost(item.postId).then(() => {refresh();}).catch(e => {console.dir(e);});" title="Delete this comment">✕</span>
<div class="jc_comments" v-show="item.visable">
<div v-for="comment in item.comments">{{ comment.date+':' }} <strong class='jc_hint' v-bind:title="comment.reply_count + '条回复'" v-show="comment.reply_count > 0">{{ comment.reply_count }}</strong><strong>{{ comment.content|removeHTMLTags }}</strong></div>
</div>
</div>
</div>
</div>
<div id="jc_btn">
<div class="jc_sync" v-show="syncing">
syncing...
</div>
<span class="show" v-on:click="show = !show">
<span v-if="show">关闭</span>
<span v-else>打开</span>
</span>
<span class="settings">设置</span>
<span class="resync" v-on:click="if(syncing)return;syncing=true;sync(()=>{syncing=false})">同步</span>
</div>
</div>
`),
//当前页面序号
jc_current_page: "",
/**
* 当页面初始化的时候触发,用于解析页面hash确定是否需要跳转。
*/
onPageLoad: function () {
var myregexp = /#comment-\d+/g;
var hash = myregexp.exec($(location).attr('hash'));
if (hash === null || hash === undefined || hash.length <= 0) return;
hash = hash[0];
if ($(hash).length > 0) {
console.log('在当前页');
var post_id = hash.split('-')[1];
storage.GetPostInfo(post_id).then(post => {
console.log('post信息', post);
if (post && post.pageNo != $.jcsaver.jc_current_page) {
post.pageNo = $.jcsaver.jc_current_page;
storage.AddPost(post).then(() => {
console.log("post更新完成", post.postId, post.pageNo);
});
}
})
return;
}
console.log('不在当前页');
if (parseInt($('.commentlist li').eq(0).attr('id').split('-')[1]) < parseInt(hash.split('-')[1])) {
var next_page = $('.next-comment-page').eq(0);
if (next_page.length == 1) {
next_page = next_page.attr('href').split('-')[1].split('#')[0];
$.jcsaver.turnPage(next_page, hash);
return;
}
}
if (parseInt($('.commentlist li').eq(-1).attr('id').split('-')[1]) > parseInt(hash.split('-')[1])) {
var next_page = $('.previous-comment-page').eq(0);
if (next_page.length == 1) {
next_page = next_page.attr('href').split('-')[1].split('#')[0];
$.jcsaver.turnPage(next_page, hash);
return;
}
}
alert("你寻找的post id:" + hash + "已经被删除!");
},
// 翻页
turnPage: function (pageNo, hash) {
var url = '//jandan.net/' + $.jcsaver.getPageKey() + '/page-' + pageNo + hash;
console.log(url);
if (confirm('你寻找的post id:' + hash + ' 在当前页不存在,是否跳转到\n' + url + '\n继续寻找?')) window.location.href = url;
},
/**
* 监听到ajax成功后执行,用于解析成功后的数据
* @param {event} event
* @param {xhr} xhr
* @param {settings} settings
*/
onAjaxSuccess: function (event, xhr, settings) {
if (settings.url == "/jandan-tucao.php" && xhr.responseJSON.code == "0") {
var data = xhr.responseJSON.data;
var value = {};
value.tucaoId = data.comment_ID;
value.postId = data.comment_post_ID;
value.date = data.comment_date;
value.content = data.comment_content;
value.author = data.comment_author;
value.page_cate = $.jcsaver.getPageKey();
value.page_no = $.jcsaver.jc_current_page;
storage.AddComment(value, true).catch(err => {
alert("保存失败!请查看控制台日志!");
});
}
},
/**
* 初始化
*/
init: function () {
//获取页面分类,比如pic、ooxx、duan
var pageKey = $.jcsaver.getPageKey();
var _this = this;
if ($.inArray(pageKey, $.jcsaver.jc_pages.map(page => page.id)) < 0) {
console.log('Jandan_tucao_saver: current page not match!');
return false;
}
//初始化存储
storage.Init().then(e => {
_this.jc_current_page = null;
//获取页码
var result = new RegExp('page-([^&#]*)').exec(window.location.href);
if (result != null) {
_this.jc_current_page = parseFloat(result[1]);
return Promise.resolve();
} else {
var next_page_url = $('.previous-comment-page').eq(0).attr('href');
net.GetHtml(next_page_url, {}, 'text').then(data => {
result = new RegExp('Newer Comments" href=([^&#]*)page-([^&#]*)').exec(data)
_this.jc_current_page = parseFloat(result[2]);
return Promise.resolve();
})
}
}).then(() => {
$.jcsaver.onPageLoad();
_this.st_index_key = _this.st_index_prefix + pageKey;
$style = $("<style></style>");
$style.text(_this.jc_css);
$("head").append($style);
$("body").append(_this.jc_html);
initVue();
jc_vue.current_page = pageKey;
jc_vue.refresh();
}).catch(err => {
console.log(err);
alert('An error occured!');
})
},
/**
* 获取当前页面分类
*/
getPageKey: function () {
if ($.jcsaver.page_cate) return $.jcsaver.page_cate;
var url = window.location.href;
var subPath = url.split("/");
$.jcsaver.page_cate = subPath[3];
return $.jcsaver.page_cate;
},
};
var jc_vue = undefined;
var initVue = function () {
jc_vue = new Vue({
el: "#jc_main",
data: {
pages: $.jcsaver.jc_pages,
items: [],
show: false,
current_page: "",
syncing: false,
},
filters: {
//根据post信息获取跳转到的页面地址
getUrl: function (post) {
return "/" + post.pageCate + "/page-" + post.pageNo + "#comment-" + post.postId;
},
/**
* 去掉HTML标签
*/
removeHTMLTags: function (string) {
return string.replace(/(<([^>]+)>)/ig, '');
}
},
methods: {
/**
* 刷新自己所有历史吐槽得到的回复,手动触发。
* @return {undefined} 回调函数
*/
sync: function (callback) {
var promiseArray = [];
jc_vue.pages.forEach(page => {
var page_key = page.id;
promiseArray.push(
new Promise((resolve, reject) => {
var subPromiseArray = [];
storage.GetPostsByCate(page_key).then(post_list => {
post_list.forEach(post => {
var promise = new Promise((subResolve, subReject) => {
var post_total_reply = 0;
storage.GetCommentsByPostId(post.postId).then(comments => {
net.GetAllTucao(post.postId).then(tucao_list => {
console.log('post', post.postId, '吐槽数量', Object.keys(tucao_list).length);
comments.forEach(comment => {
let current_comment_reply_count = 0;
for (var key in tucao_list) {
if (tucao_list.hasOwnProperty(key)) {
var tucao = tucao_list[key];
if (tucao.comment_content.includes('data-id="' + comment.tucaoId + '"')) {
// console.log('FFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUUUKKKKKKKKKKKKKKKKKKKKK', comment);
console.log('遇到回复,回复id:', tucao.comment_ID, "被回复id:", comment.tucaoId);
current_comment_reply_count += 1;
post_total_reply += 1;
break;
}
}
};
if (current_comment_reply_count != comment.reply_count) {
comment.reply_count = current_comment_reply_count;
storage.AddComment(comment, false).then(() => {
console.log('comment 更新完成', comment);
// console.log('SSSSSSSSSSSSSSSSUUUUUUUUUUUUUUUUUUUUCCCCCCCCCCCCCCCCCCCCCCCEEEEEEEEEEEEEEEEESSSSSSSSSSS');
})
}
if (post.reply_total_count != post_total_reply) {
post.reply_total_count = post_total_reply;
storage.AddPost(post).then(() => {
console.log('post 更新完成', post);
// console.log('SSSSSSSSSSSSSSSSUUUUUUUUUUUUUUUUUUUUCCCCCCCCCCCCCCCCCCCCCCCEEEEEEEEEEEEEEEEESSSSSSSSSSS');
})
}
})
subResolve();
}).catch(err => {
console.dir(err)
});
})
});
subPromiseArray.push(promise);
});
}).then(() => {
Promise.all(subPromiseArray).then((data) => {
console.log(page_key, '完成');
resolve();
}).catch(e => {
console.log(e);
alert(e);
})
})
})
)
});
Promise.all(promiseArray).then(() => {
console.log('全部完成');
if (typeof (callback) == 'function') callback();
})
},
/**
* 刷新
*/
refresh: function () {
storage.GetPostsByCate(jc_vue.current_page).then(data => {
// console.log(data);
jc_vue.items = data;
})
},
/**
* 加载post下面的所有吐槽。
*/
loadComments: function (postId, visable) {
if (visable) visable = false;
else visable = true;
try {
jc_vue.items.forEach((element, idx) => {
if (element.postId == postId) {
jc_vue.$set(jc_vue.items[idx], 'visable', visable);
if (!visable) throw Exceptions.BreakException;
storage.GetCommentsByPostId(postId).then(data => {
console.log(data);
jc_vue.$set(jc_vue.items[idx], 'comments', data);
throw Exceptions.BreakException;
}).catch(e => {
if (e !== Exceptions.BreakException) {
alert(e);
throw e;
}
})
}
});
} catch (e) {
if (e !== Exceptions.BreakException) throw e;
}
},
/**
* 删除post
*/
deletePost: function (postId) {
return new Promise((resolve, reject) => {
storage.DeletePost(postId).then(() => {
resolve();
}).catch(e => {
reject(e);
});
})
},
}
});
}
$.jcsaver.init();
$(document).ajaxSuccess((event, xhr, settings) => {
$.jcsaver.onAjaxSuccess(event, xhr, settings);
});
})();