sehuatang

self mode

  1. // ==UserScript==
  2. // @name sehuatang
  3. // @version 0.0.12
  4. // @author bilabila
  5. // @namespace https://greasyfork.org/users/164996a
  6. // @match https://www.sehuatang.org/404
  7. // @match https://sehuatang.org/404
  8. // @description self mode
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_listValues
  12. // @grant GM_deleteValue
  13. // @grant GM_xmlhttpRequest
  14. // @connect www.sehuatang.org
  15. // @run-at document-start
  16. // ==/UserScript==
  17. const tags = {
  18. fellatiojapan: ['亚洲无码原创', 'fellatiojapan'],
  19. handjobjapan: ['亚洲无码原创', 'handjobjapan'],
  20. uralesbian: ['亚洲无码原创', 'uralesbian'],
  21. spermmania: ['亚洲无码原创', 'spermmania'],
  22. legsjapan: ['亚洲无码原创', 'legsjapan'],
  23. 无: ['亚洲无码原创', '全部'],
  24. 有: ['亚洲有码原创', '全部'],
  25. 素: ['素人有码系列', '全部'],
  26. }
  27. const head = `<meta charset="UTF-8" />
  28. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  29. <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  30. <title>sehuatang</title>
  31. <style>
  32. :root {
  33. --color1: #444;
  34. --color2: #bbb;
  35. }
  36. body {
  37. color: var(--color1);
  38. text-align: center;
  39. margin: 0;
  40. box-sizing: border-box;
  41. }
  42. img {
  43. width: 100%;
  44. }
  45. ul {
  46. margin: 0;
  47. padding: 0;
  48. list-style-type: none;
  49. }
  50. li {
  51. color: var(--color1);
  52. margin: 0 6% 3em;
  53. padding: 0;
  54. }
  55. a {
  56. text-decoration: none;
  57. }
  58. .title {
  59. display: flex;
  60. justify-content: space-between;
  61. background-color: white;
  62. }
  63. #tag {
  64. margin: 1em 0;
  65. }
  66. #tag > span {
  67. display: inline-block;
  68. padding: 0.2em 0.8em;
  69. color: var(--color1);
  70. cursor: pointer;
  71. user-select: none;
  72. }
  73. #tag > span.disable {
  74. color: var(--color2);
  75. }
  76. #clear {
  77. position: absolute;
  78. top: 1em;
  79. right: 1em;
  80. padding: 0.2em;
  81. cursor: pointer;
  82. user-select: none;
  83. color: var(--color2);
  84. }
  85. #clear:hover {
  86. color: var(--color1)
  87. }
  88. </style>`
  89. const body = `<div id=app>
  90. <span id=clear>☢</span>
  91. <div id="tag"></div>
  92. <ul></ul>
  93. </div>`
  94. document.head.innerHTML = head
  95. document.body.innerHTML = body
  96. const ul = document.querySelector('ul')
  97. Array.prototype.last = function (i = 1) {
  98. return this[this.length - i]
  99. }
  100. const gmFetch = (url, method = 'GET') =>
  101. new Promise((onload, onerror) => {
  102. GM_xmlhttpRequest({ url, method, onload, onerror, cookie: '_safe=' })
  103. })
  104. const parseHTML = (str) => {
  105. const tmp = document.implementation.createHTMLDocument()
  106. tmp.body.innerHTML = str
  107. return tmp
  108. }
  109. const cache = async (k, f, ...args) => {
  110. if (k === undefined) return
  111. let a = GM_getValue(k)
  112. if (a) return JSON.parse(a)
  113. a = await f(...args)
  114. GM_setValue(k, JSON.stringify(a))
  115. return a
  116. }
  117. // get one post
  118. const fetchOnePost = async (tid) => {
  119. const url = `https://www.sehuatang.org/forum.php?mod=viewthread&tid=${tid}`
  120. let a = await gmFetch(url)
  121. a = parseHTML(a.responseText)
  122. let title = a.querySelector('h1'),
  123. img = a.querySelectorAll('.pcb img'),
  124. magnet = a.querySelector('.blockcode li'),
  125. torrent = a.querySelector('.attnm > a')
  126.  
  127. title = title ? title.textContent : ''
  128. const img_bl = [
  129. 'https://cdn.jsdelivr.net/gh/hishis/forum-master/public/images/patch.gif',
  130. ]
  131. img = img
  132. ? [...img]
  133. .map((i) => i.getAttribute('file'))
  134. .filter((i) => i && !img_bl.includes(i))
  135. : []
  136. magnet = magnet ? magnet.textContent : ''
  137. torrent = torrent ? torrent.href : ''
  138. a = { img, magnet, torrent, title }
  139. return a
  140. }
  141. // get one page
  142. const fetchOnePage = async (fid, typeid, page) => {
  143. let a = await gmFetch(
  144. `https://www.sehuatang.org/forum.php?mod=forumdisplay&fid=${fid}` +
  145. (typeid != '0' ? `&filter=typeid&typeid=${typeid}` : '') +
  146. `&page=${page}`
  147. )
  148. a = parseHTML(a.responseText)
  149. // check page
  150. // if (a.querySelector('#fd_page_top strong').textContent != page) return
  151. a = [
  152. ...a.querySelectorAll(
  153. '#threadlisttableid tbody[id^=normalthread_] th a[id^=content_]'
  154. ),
  155. ]
  156. return a.map((i) => parseInt(/content_(\d+)/.exec(i.id)[1]))
  157. }
  158. // get type and id of one fid
  159. const fetchTypeId = async (fid) => {
  160. let a = await gmFetch(
  161. `https://www.sehuatang.org/forum.php?mod=forumdisplay&fid=${fid}`
  162. )
  163. a = parseHTML(a.responseText)
  164. a = a.querySelectorAll('#thread_types > li:not([id]) > a')
  165. a = [...a].filter((i) => i.firstChild)
  166. const b = { 全部: '0' }
  167. ;[...a].forEach(
  168. (i) => (b[i.firstChild.textContent] = /typeid=(\d+)/.exec(i.href)[1])
  169. )
  170. return b
  171. }
  172. // get all fid and name
  173. const fetchFidName = async () => {
  174. const url = `https://www.sehuatang.org/forum.php`
  175. let a = await gmFetch(url)
  176. a = parseHTML(a.responseText)
  177. let ans = {}
  178. for (let b of a.querySelectorAll('a')) {
  179. if (b.childElementCount != 0) continue
  180. let fid = /(fid=|forum-)(\d+)/.exec(b.href)
  181. if (!fid) continue
  182. fid = fid[2]
  183. let name = b.textContent
  184. ans[name] = fid
  185. }
  186. return ans
  187. }
  188. class Bot {
  189. constructor(fid, typeid) {
  190. this.num_one_page = Number.MAX_SAFE_INTEGER
  191. this.key = fid + '_' + typeid
  192. this.data = JSON.parse(GM_getValue(this.key, '[]'))
  193. this.fid = fid
  194. this.typeid = typeid
  195. this.i1 = 0
  196. }
  197. async get(page) {
  198. return await fetchOnePage(this.fid, this.typeid, page)
  199. }
  200. async more(a) {
  201. if (a.page === -1) return
  202. const aa = a.arr,
  203. aal = aa.last(),
  204. b = await this.get(++a.page)
  205. if (!b) return (a.page = -1)
  206. let i = 0
  207. for (; i < b.length && b[i] >= aal; ++i) {}
  208. // end support for long time scroll interval
  209. //if (i === b.length) await this.more(a)
  210. if (i === b.length) return (a.page = -1)
  211. for (; i < b.length; ++i) aa.push(b[i])
  212. }
  213. merge() {
  214. const data = this.data
  215. if (data.length < 2) return
  216. const p = data.last(2),
  217. c = data.last(),
  218. pa = p.arr,
  219. ca = c.arr,
  220. cal = ca.last(),
  221. calen = ca.length
  222. if (cal > pa[0]) return
  223. let i = 1
  224. for (; i < pa.length && pa[i] >= cal; ++i) {}
  225. for (; i < pa.length; ++i) ca.push(pa[i])
  226. p.arr = ca
  227. if (p.page !== -1)
  228. p.page = (c.page + (ca.length - calen) / this.num_one_page) >> 0
  229. data.pop()
  230. }
  231. async refresh() {
  232. const data = this.data
  233. const p = data.last() && data.last().arr[0]
  234. data.push({
  235. arr: undefined,
  236. page: 0,
  237. })
  238. const a = data.last()
  239. a.arr = await this.get(++a.page)
  240. this.num_one_page = a.arr.length
  241. this.merge()
  242. if (p != data.last().arr[0]) {
  243. this.save()
  244. return true
  245. }
  246. }
  247. async next() {
  248. const data = this.data
  249. if (data.length === 0) return await this.refresh()
  250. await this.more(data.last())
  251. this.merge()
  252. this.save()
  253. }
  254. save() {
  255. GM_setValue(this.key, JSON.stringify(this.data))
  256. }
  257. async nextOne() {
  258. if (this.data.length === 0) await this.refresh()
  259. const a = this.data.last().arr
  260. if (this.i1 < a.length) return a[this.i1++]
  261. await this.next()
  262. return a[this.i1++]
  263. }
  264. }
  265. const li = (a) => {
  266. const { title, img, magnet, torrent } = a
  267. return `
  268. <li>
  269. ${img.map((i) => `<img src="${i}"/>`).join('')}
  270. <div class="title">
  271. <span>${title}</span>
  272. <span>
  273. <a href="${magnet}">magnet</a>
  274. <a href="${torrent}" target="_blank">torrent</a>
  275. </span>
  276. </div>
  277. </li>
  278. `
  279. }
  280. const addTag = () => {
  281. let ts = tags
  282. let t = GM_getValue('tag')
  283. t = t ? JSON.parse(t) : [Object.keys(ts)[0]]
  284. const n = document.querySelector('#tag')
  285. const a = Object.keys(ts)
  286. .map((i) => `<span ${t.includes(i) ? '' : 'class=disable'}>${i}</span>`)
  287. .join('')
  288. requestAnimationFrame(() => (n.innerHTML = a))
  289. n.addEventListener('click', (e) => {
  290. e = e.target
  291. if (e.nodeName != 'SPAN' || !e.classList.contains('disable')) return
  292. e.classList.add('disable')
  293. t = [e.textContent]
  294. GM_setValue('tag', JSON.stringify(t))
  295. window.location.reload()
  296. })
  297. return [t, ts]
  298. }
  299. const main = async () => {
  300. const [t, ts] = addTag()
  301. const v = []
  302. for (let x of t) {
  303. x = ts[t]
  304. let fid = await cache('fid', fetchFidName)
  305. fid = fid[x[0]]
  306. let typeid = await cache('typeid' + fid, fetchTypeId, fid)
  307. typeid = typeid[x[1]]
  308. v.push(new Bot(fid, typeid))
  309. }
  310. const q = await Promise.all(v.map(async (i) => await i.nextOne()))
  311. const next = async () => {
  312. let m = 0
  313. for (let i = 1; i < q.length; ++i)
  314. if (q[i] !== undefined && q[i] > q[m]) m = i
  315. if (q[m] === undefined) return
  316. const r = q[m]
  317. while (r === q[m]) {
  318. q[m] = await v[m].nextOne()
  319. }
  320. return r
  321. }
  322. let isEnd = false
  323. const add = async (a) => {
  324. if (a === undefined) {
  325. if (isEnd) return
  326. isEnd = true
  327. window.onscroll = null
  328. ul.insertAdjacentHTML(
  329. 'afterend',
  330. `<span>total : ${ul.childElementCount}</span>`
  331. )
  332. const total = document.querySelector('ul+span')
  333. new MutationObserver(
  334. () => (total.innerHTML = `total : ${ul.childElementCount}`)
  335. ).observe(ul, { childList: true })
  336. return
  337. }
  338. a = await cache(a, fetchOnePost, a)
  339. if (!a) return
  340. requestAnimationFrame(() => ul.insertAdjacentHTML('beforeend', li(a)))
  341. }
  342. for (let i = 0; i < 3; ++i) await add(await next())
  343. const a = await Promise.all(v.map(async (i) => await i.refresh()))
  344. if (a.some((i) => i)) window.location.reload()
  345. let isFetching = false
  346. window.onscroll = async () => {
  347. if (
  348. !isFetching &&
  349. 10 * window.innerHeight + window.scrollY >= document.body.offsetHeight
  350. ) {
  351. isFetching = true
  352. await add(await next())
  353. isFetching = false
  354. }
  355. }
  356. }
  357. const init = () => {
  358. for (let i of GM_listValues()) GM_deleteValue(i)
  359. GM_setValue('version', GM_info.script.version)
  360. window.location.reload()
  361. }
  362. window.onbeforeunload = () => window.scrollTo(0, 0)
  363. if (GM_getValue('version') != GM_info.script.version) init()
  364. document.querySelector('#clear').onclick = init
  365. main()