Mediawiki>夕舞八弦 |
|
| (未显示1个用户的3个中间版本) |
| 第1行: |
第1行: |
| $(function(){ | | $(() => { |
| if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)){ | | const notificationIcon = 'https://img.moegirl.org.cn/common/thumb/f/f6/%E8%93%9D%E8%90%8C%E5%AD%97.png/233px-%E8%93%9D%E8%90%8C%E5%AD%97.png' |
| return false
| | const workerUrl = window.mw ? |
| | 'https://www.hmoegirl.info/index.php?title=%E7%94%A8%E6%88%B7:%E6%9D%B1%E6%9D%B1%E5%90%9B/js/notification.js/worker.js&action=raw&ctype=text/javascript' : |
| | 'worker.js' |
| | |
| | checkNotificationPermission() |
| | main() |
| | |
| | async function main() { |
| | const worker = await registerService() |
| | const configListener = new WorkerDataListener(worker, 'config') |
| | const uiController = initUI({ onSaveConfig }) |
| | |
| | configListener.pullData() |
| | |
| | configListener.addListener(data => { |
| | uiController.updateConfig(data) |
| | }) |
| | |
| | function onSaveConfig(config) { |
| | worker.postMessage({ type: 'saveConfig', data: { config } }) |
| | alert('配置已保存') |
| } | | } |
| var lock=setInterval(function(){
| | } |
| if(mw.Api){
| |
| clearInterval(lock);
| |
| function nct(nct, url, life) {
| |
| var life = life || 8000
| |
| if (url) { nct.onclick = function () { open(url, '_blank') } }
| |
| nct.addEventListener('click', function () {
| |
| setTimeout(function () {
| |
| nct.close()
| |
| })
| |
| }, 500)
| |
| setTimeout(function () {
| |
| nct.close()
| |
| }, life)
| |
| }
| |
|
| |
|
| function setCookie(cname, cvalue, GMTStr) {
| | navigator.serviceWorker.addEventListener('message', e => { |
| var expires = "expires=" + GMTStr; | | const { type, data } = e.data |
| document.cookie = cname + "=" + cvalue + "; " + expires; | | if (type === 'sendNotification') { |
| } | | const [title, options] = data |
| | const link = options.data.link |
| | new Notification(title, options).onclick = () => window.open(link, '_blank') |
| | } |
| | }) |
| | |
| | function initUI({ |
| | onSaveConfig |
| | }) { |
| | const template = ` |
| | <div class="widget-notification-config" style="display:none;"> |
| | <div class="body"> |
| | <fieldset> |
| | <legend>设置</legend> |
| | <form class="settings" action="javascript:void(0)"> |
| | <label> |
| | <span>开启</span> |
| | <input type="checkbox" name="enable"> |
| | </label> |
| | |
| | <label> |
| | <div>排除条目:</div> |
| | <textarea placeholder="当其中的条目发生变化时不会通知,使用换行分割" name="excludeList"></textarea> |
| | </label> |
| | |
| | <div class="buttonGroup"> |
| | <button class="saveConfig">保存</button> |
| | <button class="closeModal">关闭</button> |
| | </div> |
| | </form> |
| | </fieldset> |
| | </div> |
| | </div> |
| | ` |
| | |
| | const cssStyle = ` |
| | <style> |
| | .widget-notification-config { |
| | position: fixed; |
| | top: 0; |
| | left: 0; |
| | right: 0; |
| | bottom: 0; |
| | background-color: rgba(0, 0, 0, 0.2); |
| | font-size: 14px; |
| | z-index: 100; |
| | } |
| | |
| | .widget-notification-config > .body { |
| | background-color: white; |
| | width: 300px; |
| | box-sizing: border-box; |
| | padding: 10px; |
| | border-radius: 5px; |
| | border: 2px #ccc ridge; |
| | position: absolute; |
| | top: 50%; |
| | left: 50%; |
| | transform: translate(-50%, -50%); |
| | } |
| | |
| | .widget-notification-config > .body fieldset { |
| | margin: 0; |
| | padding-left: 15px; |
| | padding-right: 15px; |
| | } |
| | |
| | .widget-notification-config > .body .settings > label { |
| | display: block; |
| | } |
| | |
| | .widget-notification-config > .body .settings > label > * { |
| | vertical-align: middle; |
| | } |
| | |
| | .widget-notification-config > .body .settings textarea[name="excludeList"] { |
| | width: -webkit-fill-available; |
| | resize: none; |
| | margin-top: 5px; |
| | height: 100px; |
| | border: 1px #666 solid; |
| | } |
| | |
| | .widget-notification-config > .body .settings .buttonGroup { |
| | margin: 0 auto; |
| | margin-top: 10px; |
| | display: table; |
| | } |
| | |
| | .widget-notification-config > .body .settings .buttonGroup button { |
| | margin: 0 5px; |
| | } |
| | </style> |
| | ` |
| | |
| | $('body').append(template).append(cssStyle) |
| | |
| | const rootEl = $('.widget-notification-config') |
| | const settingsEl = rootEl.find('.settings') |
| | |
| | // 注入按钮 |
| | $('#p-cactions ul').append('<li id="widget-notification-showConfig"><a title="实时通知">实时通知</a></li>') |
| | |
| | // modal显示事件 |
| | $('#widget-notification-showConfig').on('click', () => rootEl.show()) |
| | |
| | // modal关闭事件 |
| | rootEl.find('.closeModal').on('click', () => rootEl.hide()) |
| | |
| | // 保存配置 |
| | rootEl.find('.saveConfig').on('click', e => { |
| | const formValues = { |
| | enable: settingsEl.find('[name="enable"]').prop('checked'), |
| | listenNotification: settingsEl.find('[name="listenNotification"]').prop('checked'), |
| | excludeList: settingsEl.find('[name="excludeList"]').val().trim().split('\n') |
| | } |
| | |
| | onSaveConfig(formValues) |
| | }) |
| | |
| | function updateConfig({ enable, listenNotification, excludeList }) { |
| | settingsEl.find('[name="enable"]').prop('checked', enable) |
| | settingsEl.find('[name="listenNotification"]').prop('checked', listenNotification) |
| | settingsEl.find('[name="excludeList"]').val(excludeList.join('\n')) |
| | } |
| | |
| | return { |
| | updateConfig |
| | } |
| | } |
| | |
| | function registerService() { |
| | return new Promise(async (resolve, reject) => { |
| | if (Notification === undefined || navigator.serviceWorker === undefined) { |
| | alert('当前浏览器不支持通知插件,请从您的用户页中移除!') |
| | return reject() |
| | } |
| | |
| | if(Notification.permission === 'default') { |
| | alert('点击确定关闭这条消息后,将弹出授予通知权限的窗口,届时请点击“允许”,以保证插件的正常运行。') |
| | } |
| | |
| | await new Promise(Notification.requestPermission) |
| | const status = Notification.permission |
| | |
| | if (status === 'granted') { |
| | if (localStorage.getItem('moegirl-widget-notification-permission-granted') !== 'true') { |
| | localStorage.setItem('moegirl-widget-notification-permission-granted', 'true') |
| | new Notification('欢迎', { |
| | body: '您已经成功开启实时通知功能,欢迎使用!', |
| | icon: notificationIcon |
| | }) |
| | } |
| | } |
| | |
| | if (status === 'denied') { |
| | alert('您未能正确授予通知权限,若要继续使用,请在url栏左侧点击小锁标志开启权限,若无法设置,请从你的用户页删去此插件。') |
| | return reject() |
| | } |
| | |
| | const serviceWorkerRegistration = await navigator.serviceWorker.register(workerUrl) |
| | await navigator.serviceWorker.ready |
| | resolve(serviceWorkerRegistration.active) |
| | }) |
| | } |
|
| |
|
| function getCookie(cname) {
| | function checkNotificationPermission() { |
| var name = cname + "=";
| | if (localStorage.getItem('moegirl-widget-notification-permission-granted') === 'true' && Notification.permission !== 'granted') { |
| var ca = document.cookie.split(';');
| | localStorage.removeItem('moegirl-widget-notification-permission-granted') |
| for (var i = 0; i < ca.length; i++) {
| | } |
| var c = ca[i].trim();
| | } |
| if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
| | |
| }
| | class WorkerDataListener { |
| return "";
| | broadcastName = '' |
| }
| | worker = null |
| | data = null |
| | #incrementPullId = 0 |
|
| |
|
| function serviceToken(flag) {
| | get pullEventName() { return 'get-' + this.broadcastName } |
| var flag = flag || false
| | get broadcastChannelName() { return 'channel-' + this.broadcastName } |
| var d = new Date()
| | |
| if (flag) {
| | constructor(worker, broadcastName) { |
| d.setTime(d.getTime() + (2 * 60 * 1000))
| | this.broadcastName = broadcastName |
| setCookie('widget-notification-serviceOn', 'true', d.toGMTString())
| | this.worker = worker |
| } else {
| | this.broadcastChannel = new BroadcastChannel(this.broadcastChannelName) |
| d.setTime(d.getTime() - 1)
| | |
| setCookie('widget-notification-serviceOn', '', d.toGMTString())
| | this.addListener(data => this.data = data) |
| }
| | } |
| }
| | |
| | addListener(handler) { |
| | const usingHandler = e => { |
| | const { type, data, id } = e.data |
| | type === 'broadcast' && handler(data, id) |
| | } |
|
| |
|
| function isObjectValueEqual(a, b) {
| | this.broadcastChannel.addEventListener('message', usingHandler) |
| var aProps = Object.getOwnPropertyNames(a);
| | return () => this.broadcastChannel.removeEventListener('message', usingHandler) |
| var bProps = Object.getOwnPropertyNames(b);
| | } |
| | | |
| if (aProps.length != bProps.length) {
| | pullData() { |
| return false;
| | const pullId = this.#incrementPullId++ |
| }
| | this.worker.postMessage({ type: this.pullEventName, data: { id: pullId } }) |
| | | return pullId |
| for (var i = 0; i < aProps.length; i++) {
| | } |
| var propName = aProps[i];
| | |
| if (a[propName] !== b[propName]) {
| | getData() { |
| return false;
| | return new Promise(resolve => { |
| }
| | const pullId = this.pullData() |
| }
| | const removeListener = this.addListener((data, id) => { |
| return true;
| | if (id !== pullId) { return } |
| }
| | resolve(data) |
| | | removeListener() |
| if (typeof Notification == 'undefined') {
| | }) |
| mw.notify('你的浏览器版本过低或不支持某些功能,无法使用Web Notification插件!', { type: 'warn' })
| | }) |
| setTimeout(function () {
| | } |
| mw.notify('请从你的用户页移除该插件,以消除这些警告!', { type: 'warn' });
| | } |
| }, 2000)
| |
| return false
| |
| }
| |
| | |
| if (Notification.permission == 'default') {
| |
| alert('点击确定关闭这条消息后,将弹出授予通知权限的窗口,届时请点击“允许”,以保证插件的正常运行。')
| |
| }
| |
| | |
| Notification.requestPermission(function () {
| |
| var status = Notification.permission
| |
| if (status == 'granted') {
| |
| if (!localStorage.getItem('widget-notification-messageMark')) {
| |
| localStorage.setItem('widget-notification-messageMark', 'true')
| |
| nct(new Notification('欢迎', {
| |
| body: '您已经成功开启通知功能,欢迎使用!',
| |
| icon: 'https://fgo.wiki/resources/assets/wiki.png'
| |
| }))
| |
| }
| |
| }
| |
| if (status == 'denied') {
| |
| alert('您未能正确授予通知权限,若要继续使用,请在url栏左侧点击小锁标志开启权限,若无法设置,请从你的用户页删去此插件。')
| |
| }
| |
| })
| |
| | |
| var api = new mw.Api()
| |
| | |
| var userName = ''
| |
| api.get({
| |
| "action": "query",
| |
| "format": "json",
| |
| "meta": "userinfo",
| |
| "utf8": 1
| |
| }).done(function (data) {
| |
| userName = data.query.userinfo.name
| |
| })
| |
| | |
| var pages = []
| |
| var pagesStr = ''
| |
| var apiContinue = null
| |
| function getWatch() {
| |
| var request = {
| |
| "action": "query",
| |
| "format": "json",
| |
| "list": "watchlistraw",
| |
| "utf8": 1,
| |
| "wrlimit": "max"
| |
| }
| |
| if (apiContinue) {
| |
| request.wrcontinue = apiContinue.wrcontinue
| |
| request['continue'] = apiContinue['continue']
| |
| } else {
| |
| delete request.wrcontinue
| |
| delete request['continue']
| |
| }
| |
| api.get(request).done(function (data) {
| |
| apiContinue = data['continue']
| |
| var watch = data.watchlistraw
| |
| $.each(watch, function () {
| |
| if (this.title != 'User talk:' + userName) {
| |
| pages.push(this.title)
| |
| }
| |
| })
| |
| pagesStr = pages.join('|')
| |
| localStorage.setItem('widget-notification-watchList', pagesStr)
| |
| apiContinue && getWatch()
| |
| })
| |
| }
| |
| | |
| if (location.href == encodeURIComponent('https://fgo.wiki/w/Special:编辑监视列表')) {
| |
| if (/已从您的监视列表移除%d{1,2}个标题/.test($('#mw-content-text').text())) {
| |
| pages = []
| |
| getWatch()
| |
| }
| |
| }
| |
| | |
| function setWatch(act) {
| |
| var act = act || 'push'
| |
| var watch = localStorage.getItem('widget-notification-watchList').split('|')
| |
| var thisTitle = location.href.replace(/https\:\/\/fgo\.wiki\/w\/(.+)$/, '$1')
| |
| if (/(index\.php|action=edit)/.test(thisTitle)) {
| |
| thisTitle = thisTitle.replace(/.*title=(.+?)(&.+|$)/, '$1')
| |
| }
| |
| thisTitle = decodeURIComponent(thisTitle)
| |
| var talkTitle = 'Talk:' + thisTitle
| |
| var npRE = /(Template|Mooncell|模块|User|Help):(.+)/
| |
| if (npRE.test(thisTitle)) {
| |
| var prefix = thisTitle.replace(npRE, '$1')
| |
| var name = thisTitle.replace(npRE, '$2')
| |
| }
| |
| if (/(Template|Mooncell|User|Help)/.test(prefix)) {
| |
| talkTitle = prefix + ' talk:' + name
| |
| }
| |
| if (prefix == '模块') {
| |
| talkTitle = '模块讨论:' + name
| |
| }
| |
| if (act == 'push') {
| |
| var flag = true
| |
| $.each(watch, function () {
| |
| if (this == thisTitle || this == talkTitle) { flag = false }
| |
| })
| |
| flag && watch.push(thisTitle, talkTitle)
| |
| }
| |
| (act == 'remove') && $.each(watch, function (i) {
| |
| if (this == thisTitle || this == talkTitle) {
| |
| watch[i] = ''
| |
| }
| |
| })
| |
| | |
| var watchStr = watch.join('|')
| |
| localStorage.setItem('widget-notification-watchList', watchStr)
| |
| }
| |
| | |
| $('#ca-watch, #ca-unwatch').mousedown(function () {
| |
| (this.id.toLowerCase() == 'ca-watch') && setWatch()
| |
| ; (this.id.toLowerCase() == 'ca-unwatch') && setWatch('remove')
| |
| })
| |
| $('#mw-editpage-watch').mouseup(function () {
| |
| $('#wpWatchthis').is(':checked') ? setWatch('remove') : setWatch() // 此处获得了false为选中,true为未选中
| |
| })
| |
| | |
| getCookie('widget-notification-serviceOn') != 'true' && main()
| |
| var watchdogKey = setInterval(function () {
| |
| if (getCookie('widget-notification-serviceOn') != 'true') {
| |
| main()
| |
| clearInterval(watchdogKey)
| |
| }
| |
| }, 5000 + Math.random())
| |
| | |
| | |
| function main() {
| |
| console.log('notification.js已开启服务,开始进行消息与监视列表的通知推送。')
| |
| serviceToken(true)
| |
| ; (function serviceStart() {
| |
| setTimeout(function () {
| |
| serviceToken(true)
| |
| serviceStart()
| |
| }, 60 * 1000)
| |
| })()
| |
| | |
| $(window).on('unload', function () {
| |
| serviceToken()
| |
| })
| |
| | |
| if (!getCookie('widget-notification-removeTime')) {
| |
| var d = new Date()
| |
| d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
| |
| setCookie('widget-notification-removeTime', d.toGMTString(), d.toGMTString())
| |
| }
| |
| | |
| ; (function () {
| |
| function getTimeISO8061() {
| |
| var fun = function (num) {
| |
| return num < 10 ? '0' + num : num
| |
| }
| |
| var time = new Date()
| |
| var y = time.getUTCFullYear(),
| |
| m = fun(time.getUTCMonth() + 1),
| |
| d = fun(time.getUTCDate()),
| |
| h = fun(time.getUTCHours()),
| |
| i = fun(time.getUTCMinutes()),
| |
| s = fun(time.getUTCSeconds())
| |
| var timeStr = y + '-' + m + '-' + d + 'T' + h + ':' + i + ':' + s + 'Z'
| |
| return timeStr
| |
| }
| |
| | |
| setTimeout(function () {
| |
| getWatch()
| |
| console.log('notification.js:已获取监视列表')
| |
| }, 30000)
| |
| | |
| var lastSearchTime = getTimeISO8061()
| |
| setInterval(function () {
| |
| var usedStr = getCookie('widget-notification-usedIds')
| |
| if (usedStr) {
| |
| var used = usedStr.split('|')
| |
| } else {
| |
| var used = []
| |
| }
| |
| | |
| var rcidsStr = getCookie('widget-notification-rcids')
| |
| if (rcidsStr) {
| |
| var rcids = rcidsStr.split('|')
| |
| } else {
| |
| var rcids = []
| |
| }
| |
| | |
| var memoryTime = getCookie('widget-notification-lastSearchTime')
| |
| if (memoryTime) {
| |
| lastSearchTime = memoryTime
| |
| }
| |
| api.get({
| |
| action: 'query',
| |
| format: 'json',
| |
| prop: '',
| |
| list: 'recentchanges',
| |
| rcend: lastSearchTime,
| |
| rclimit: 'max',
| |
| utf8: 1,
| |
| formatversion: 1,
| |
| rctoponly: 1,
| |
| rcexcludeuser: userName,
| |
| rcprop: 'title|user|comment|ids'
| |
| }).done(function (data) {
| |
| var d = new Date()
| |
| d.setTime(d.getTime() + (30 * 60 * 1000))
| |
| setCookie('widget-notification-lastSearchTime', getTimeISO8061(), d.toGMTString())
| |
| var query = data.query.recentchanges
| |
| var filter = []
| |
| var pages = localStorage.getItem('widget-notification-watchList').split('|')
| |
| $.each(query, function () {
| |
| for (var i = 0; i < pages.length; i++) {
| |
| if (this.title == pages[i]) {
| |
| var flag = true
| |
| for (var j = 0; j < filter.length; j++) {
| |
| if (isObjectValueEqual(this, filter[j])) {
| |
| flag = false
| |
| }
| |
| }
| |
| flag && filter.push(this)
| |
| }
| |
| }
| |
| })
| |
| | |
| $.each(filter, function () {
| |
| var mark = true
| |
| for (var i = 0; i < rcids.length; i++) {
| |
| if (this.rcids == rcids[i]) {
| |
| mark = false
| |
| }
| |
| }
| |
| | |
| if (mark) {
| |
| var commentStr = '在【' + this.title + '】作出了编辑。'
| |
| if (this.comment) {
| |
| this.comment = this.comment.replace('// Edit via Wikiplus', '')
| |
| var chapterRE = /\/\* (.+?) \*\/([^\/]*)/
| |
| if (chapterRE.test(this.comment)) {
| |
| var chapter = this.comment.replace(chapterRE, '$1')
| |
| var comment = this.comment.replace(chapterRE, '$2').replace(/[\s]*(.+)[\s]*/, '$1')
| |
| if (chapter && (chapter != this.comment)) {
| |
| commentStr += '\n章节 → ' + chapter
| |
| }
| |
| if (comment && (comment != this.comment)) {
| |
| commentStr += '\n摘要:' + comment
| |
| }
| |
| } else {
| |
| commentStr += '\n摘要:' + this.comment.replace(/[\s]*(.+)[\s]*/, '$1')
| |
| }
| |
| | |
| }
| |
| nct(new Notification(this.user, {
| |
| body: commentStr,
| |
| icon: '//fgo.wiki/extensions/Avatar/avatar.php?user=' + this.user
| |
| }), 'https://fgo.wiki/index.php?title=' + this.title + '&curid=' + this.pageid + '&diff=' + this.revid + '&oldid=' + this.old_revid)
| |
| console.log('编辑者:' + this.user + '\n' + commentStr)
| |
| rcids.push(this.rcid)
| |
| }
| |
| var rcidsStr = rcids.join('|')
| |
| setCookie('widget-notification-rcids', rcidsStr, getCookie('widget-notification-removeTime'))
| |
| })
| |
| })
| |
| | |
| api.get({
| |
| action: "query",
| |
| format: "json",
| |
| prop: "",
| |
| meta: "notifications",
| |
| utf8: 1,
| |
| notfilter: "!read",
| |
| notlimit: "max"
| |
| }).done(function (data) {
| |
| var query = data.query.notifications.list
| |
| $.each(query, function () {
| |
| var msgNct = function (text, $this) {
| |
| nct(new Notification($this.agent.name + ':', {
| |
| body: text,
| |
| icon: '//fgo.wiki/extensions/Avatar/avatar.php?user=' + $this.agent.name,
| |
| sticky: true
| |
| }), 'https://fgo.wiki/w/' + $this.title.full, 9999999999)
| |
| console.log($this.agent.name + ':' + text)
| |
| }
| |
| | |
| var mark = true
| |
| for (var i = 0; i < used.length; i++) {
| |
| if (this.id == used[i]) {
| |
| mark = false
| |
| }
| |
| }
| |
| | |
| if (mark) {
| |
| switch (this.type) {
| |
| case 'edit-thank': {
| |
| msgNct('对您在【' + this.title.full + '】的编辑表示了感谢!', this)
| |
| break
| |
| }
| |
| case 'edit-user-talk': {
| |
| msgNct('在您的讨论页留言了。', this)
| |
| break
| |
| }
| |
| case 'flowthread_reply': {
| |
| msgNct('回复了您在【' + this.title.full + '】的评论。', this)
| |
| break
| |
| }
| |
| case 'flowthread_userpage': {
| |
| msgNct('在您的用户页留下了评论。', this)
| |
| break
| |
| }
| |
| case 'flowthread_delete': {
| |
| nct(new Notification('通知', {
| |
| body: '您在【' + this.title.full + '】下的评论被删除了。',
| |
| icon: 'https://fgo.wiki/resources/assets/wiki.png',
| |
| sticky: true
| |
| }), 'https://fgo.wiki/w/' + this.title.full, 9999999999)
| |
| console.log('评论被删除:在页面【' + this.title.full + '】上')
| |
| break
| |
| }
| |
| case 'mention': {
| |
| msgNct('在【' + this.title.full + '】中提到了您。', this)
| |
| break
| |
| }
| |
| }
| |
| used.push(this.id)
| |
| }
| |
| })
| |
| var usedStr = used.join('|')
| |
| setCookie('widget-notification-usedIds', usedStr, getCookie('widget-notification-removeTime'))
| |
| })
| |
| | |
| }, 60000)
| |
| | |
| })()
| |
| | |
| }
| |
| }
| |
| },1)
| |
| }) | | }) |