Skip to content

Instantly share code, notes, and snippets.

@iwinux
Last active May 28, 2018 02:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iwinux/796f8b3b2da5d3aa8fc686dc4a44c8b8 to your computer and use it in GitHub Desktop.
Save iwinux/796f8b3b2da5d3aa8fc686dc4a44c8b8 to your computer and use it in GitHub Desktop.
UI / UX Tweaks for BearyChat
// ==UserScript==
// @name barely-tweaks
// @namespace http://tampermonkey.net/
// @version 0.6
// @description UI / UX Tweaks for BearyChat
// @author E.T
// @match https://*.bearychat.com/*
// ==/UserScript==
/* global document, fetch */
/* eslint-disable no-await-in-loop, no-underscore-dangle */
const FAVICON_URL =
'https://cdn.bearychat.com/chat/static/media/favicon.6ae2a8c8.ico'
function findInjectPosition() {
return document.querySelector('.sidePanel > :first-child')
}
function getProp(obj, keyPath) {
let value = obj
for (const key of keyPath) {
value = value[key]
if (!(value && typeof value === 'object')) {
break
}
}
return value
}
function getReactInternalProp(selector, keyPath) {
const elem = document.querySelector(selector)
const key = Object.keys(elem).find(item =>
item.startsWith('__reactInternalInstance$')
)
if (key === undefined) {
console.error('cannot find React instance')
return null
}
const fullKeyPath = [key, '_currentElement', '_owner'].concat(keyPath)
const value = getProp(elem, fullKeyPath)
if (!value) {
console.warn(`no valid value found at ${fullKeyPath}`)
}
return value
}
const getReduxStore = () =>
getReactInternalProp('[data-reactroot]', ['_context', 'store'])
const sleep = ms =>
new Promise(resolve => {
setTimeout(resolve, ms)
})
function* getUnreadChannels() {
const store = getReduxStore()
if (!store) {
console.warn('cannot find Redux store')
return
}
const channels = store
.getState()
.vchannels.get('vchannels')
.values()
for (const channel of channels) {
if (channel.get('unread_count') > 0) {
yield channel.get('vchannel_id')
}
}
}
const markAsRead = chanId =>
// eslint-disable-next-line compat/compat
fetch(`https://ein.bearychat.com/api/vchannels/${chanId}/mark_read`, {
method: 'POST',
credentials: 'include',
}).then(resp => resp.json())
const markAllAsRead = async () => {
for (const chanId of getUnreadChannels()) {
await markAsRead(chanId)
await sleep(100)
}
}
const makeLink = (label, onClick) => {
const link = document.createElement('a')
link.href = '#'
link.className = 'barely-tweaks-link'
link.style.marginRight = '10px'
link.innerText = label
link.addEventListener('click', evt => {
evt.preventDefault()
onClick(evt)
})
return link
}
const makeToggleLink = (label, altLabel, onClick) => {
let toggle = false
const link = makeLink(label, ({ target }) => {
if (toggle) {
target.innerText = target.dataset.origLabel
} else {
target.innerText = target.dataset.altLabel
}
toggle = !toggle
onClick(target, toggle)
})
link.dataset.origLabel = label
link.dataset.altLabel = altLabel
return link
}
const installLinks = (...links) => {
const injectPosition = findInjectPosition()
if (injectPosition.querySelector('.barely-tweaks-container')) {
return
}
const container = document.createElement('div')
for (const link of links) {
container.appendChild(link)
}
container.classList.add('barely-tweaks-container')
container.style.fontSize = '12px'
container.style.marginTop = '20px'
injectPosition.appendChild(container)
injectPosition.style.flexWrap = 'wrap'
injectPosition.style.height = 'initial'
injectPosition.style.paddingTop = '20px'
injectPosition.style.paddingBottom = '20px'
}
const main = () => {
let timerId
const linkFixFavicon = makeToggleLink(
'禁止变换 Favicon',
'恢复 Favicon 变换',
(target, enable) => {
if (!enable) {
clearInterval(timerId)
return
}
timerId = setInterval(() => {
const favicon = document.querySelector('link[rel="shortcut icon"]')
if (!favicon) {
return
}
favicon.href = FAVICON_URL
}, 1000)
}
)
const linkMarkAllAsRead = makeLink('全部标为已读', markAllAsRead)
installLinks(linkFixFavicon, linkMarkAllAsRead)
setInterval(() => {
installLinks(linkFixFavicon, linkMarkAllAsRead)
}, 30 * 1000)
}
const bootstrap = (delay = 300) => {
if (!findInjectPosition()) {
setTimeout(bootstrap, delay, delay * 2)
return
}
main()
}
if (/complete|interactive|loaded/.test(document.readyState)) {
bootstrap()
} else {
document.addEventListener('DOMContentLoaded', bootstrap)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment