吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2141|回复: 23
上一主题 下一主题
收起左侧

[其他原创] 阅读APP网页端脱机运行

[复制链接]
跳转到指定楼层
楼主
梦汐 发表于 2024-3-2 18:06 回帖奖励
功能大概就是开启阅读APP的WEB以后,将接口响应内容缓存到本地后,就可以关闭阅读APP的WEB服务了,避免手机过度耗电和APP被杀后台导致的连接断开

其他部分有版权问题,仅贴我自己写的代码,大概就是利用IDB做的纯脱机运行的(50MB左右缓存上限)

class MessageTip {
    constructor(tip = null, css = null) {
        this.tip = tip || ""
        this.node = $('<div/>', {
            text: this.tip,
            css: css || {
                zIndex: 999,
                fontSize: '1rem',
                color: 'rgb(255, 255, 255)',
                backgroundColor: 'rgba(0, 0, 0, 0.6)',
                padding: '10px 15px',
                margin: '0px 0px 0px -60px',
                borderRadius: '4px',
                position: 'fixed',
                top: '50%',
                left: '50%',
                width: '130px',
                textAlign: 'center'
            }
        })
        $("body").append(this.node)
    }

    text(text) {
        this.tip = text
        $(this.node).text(text)
    }

    show() {
        $(this.node).show()
    }

    hide() {
        $(this.node).hide()
    }

    open(time = null) {
        if (this.hold != undefined) {
            clearInterval(this.hold)
        }
        this.index = -1
        this.list = [".", "..", "..."]
        this.text(this.tip + this.list[2])
        this.hold = setInterval(() => {
            if (++this.index >= this.list.length) {
                this.index = 0
            }
            this.text(this.tip + this.list[this.index])
        }, time || 750)
    }

    off() {
        clearInterval(this.hold)
    }
}

function CheckConnection() {
    return new Promise(
        (resolve) => {
            var xhr = new XMLHttpRequest()
            var url = window.host + "/" + "getReadConfig"
            xhr.timeout = 1500
            xhr.open('GET', url, true)
            xhr.onload = function () {
                resolve(true)
            }
            xhr.ontimeout = function (e) {
                console.log("ontimeout", e);
                resolve(false)
            }
            xhr.onerror = function (e) {
                console.log("onerror", e);
                resolve(false)
            }
            xhr.send(null)
        }
    )
}

class Offline {
    constructor(cb) {
        this.update().then(cb)
    }

    get(key) {
        return new Promise(
            completed => {
                ldb.get(key, function (value) {
                    completed(value)
                })
            }
        )
    }

    set(key, value) {
        ldb.set(key, value)
    }

    update() {
        return new Promise(
            async completed => {
                var obj = (await this.get("offline")) || "[]"
                this.data = JSON.parse(obj)
                this.keys = []
                for (let key in this.data) {
                    this.keys.push(this.data[key]['url'])
                }
                console.log("缓存数量", this.keys.length)
                this.size(obj).then(
                    size => console.log(size)
                )
                completed(this)
            }
        )
    }

    find(key, value) {
        let enumerate = this.data
        for (let index in enumerate) {
            if (enumerate[index][key] == value) {
                return enumerate[index]['data']
            }
        }
        return null
    }

    save(object) {
        if (this.keys.indexOf(object['url']) != -1) {//重复写入
            return null
        }
        this.keys.push(object['url'])
        this.data.push(object)
        this.data = this.data.slice(this.data.length > 1500 ? this.data.length - 1500 : 0)
        this.set("offline", JSON.stringify(this.data))
    }

    async size(obj = null) {
        function calculateStringSize(str) {
            var bytes = str.length * 2;
            var kb = bytes / 1024;
            var mb = kb / 1024;
            return { bytes: bytes, kb: kb, mb: mb };
        }
        return calculateStringSize(obj || (await offline.get("offline")))
    }
}

function request(url, options = null, of = null) {
    var exp = function (value) {
        this.value = value;
    }
    exp.prototype.json = function () {
        return JSON.parse(this.value)
    }
    exp.prototype.text = function () {
        return this.value
    }
    if (of == null) {
        of = !window.online || false
    }
    return new Promise(
        async (completed) => {
            async function send() {
                let value = await (await fetch(url, options)).text()
                offline.save({
                    "url": url,
                    "data": value
                })
                return value
            }
            if (of) { // 离线
                let value = offline.find("url", url)
                if (value != null) {
                    completed(new exp(value))
                    console.log("离线加载", url)
                } else {
                    console.log("找不到相关缓存", url)
                    completed(false)
                }
            } else {
                completed(new exp(await send()))
            }
        }
    )
}

class Books {
    constructor() {
        this.wrapper = $(".wrapper")[0]
        this.book_num = null
        this.book_info = null
    }
    async update() {
        this.book_shelf = (await request(`${host}/getBookshelf`)).json()['data']
        this.delete()
        for (let i in this.book_shelf) {
            let info = this.book_shelf[i]

            let dv = "data-v-79a0da0d"
            let img = encodeURIComponent(info['coverUrl'])
            let name = info['name']
            let author = info['author'] || "未知"
            let size = info['totalChapterNum'] || null
            let date = Math.floor((Date.now() - parseInt(info['lastCheckTime'])) / (1000 * 60 * 60))

            let node = $(`<div ${dv}="" class="book">
                <div ${dv}="" class="cover-img"><img ${dv}="" class="cover"
                        src="${host}/cover?path=${img}"
                        alt=""></div>
                <div ${dv}="" class="info">
                    <div ${dv}="" class="name">${name}</div>
                    <div ${dv}="" class="sub">
                        <div ${dv}="" class="author">${author}</div><!---->
                        <div ${dv}="" class="update-info">
                            <div ${dv}="" class="dot">•</div>
                            <div ${dv}="" class="size">共${size}章</div>
                            <div ${dv}="" class="dot">•</div>
                            <div ${dv}="" class="date">${date}小时前</div>
                        </div>
                    </div><!---->
                    <div ${dv}="" class="dur-chapter"> 已读:${info['durChapterTitle']}</div>
                    <div ${dv}="" class="last-chapter">最新:${info['latestChapterTitle']}</div>
                </div>
            </div>`)
            $(this.wrapper).append(node)
            $(node).on("click", function (method, _this, context) {
                return function () {
                    method.call(_this, context)
                }
            }(this.openbook, this, i))
        }
        this.book_num = -1
    }
    async directory() {
        var url = host + `/getChapterList?url=${encodeURIComponent(this.book_info['bookUrl'])}`
        try {
            this.chapter_list = (await request(url)).json()['data']
        } catch (error) {
            console.log("读目录失败,请检查缓存/网络",error)
            books.visual()
            return false
        }
        if (window.online) { // 最后阅读的索引
            this.chapter_index = this.book_info['durChapterIndex']
        } else {
            let value = JSON.parse(localStorage.getItem(this.book_info['name']))
            if (value == null) {
                this.chapter_index = this.book_info['durChapterIndex']
            } else {
                this.chapter_index = value['index']
            }
            console.log("使用本地进度", this.chapter_index)
        }

        $('.chapter-box-bd').html("") // 清空章节目录
        for (let i in this.chapter_list) {
            let title = `${this.chapter_list[i]['title']}`
            let node = $(`<div class="chapter">${title}</div>`)
            this.chapter_list[i]['node'] = node
            $('.chapter-box-bd').append(node)
        }
        this.openchapter(this.chapter_index)
    }
    openchapter(index = null) {
        return new Promise(
            async complete => {
                var src = host + `/getBookContent?url=${encodeURIComponent(this.book_info['bookUrl'])}&index=${index || this.chapter_index}`
                var content = await request(src)
                if (content == false) { // 获取失败
                    console.log("请求失败了呢!", src)
                    return complete(false)
                }
                content = content.json()['data']
                this.text(content)
                var title = this.chapter_list[index || this.chapter_index]['title']
                this.title(title)
                this.sprogress(index || this.chapter_index)
                $('#frame1')[0].style = ''
                $(".select").toggleClass("select", false)
                $(this.chapter_list[index || this.chapter_index]['node']).toggleClass("select", true)
                complete(true)
            }
        )
    }
    sprogress(index) {
        if (window['online'] == false) {
            console.log("离线模式不支持上传进度") // 待:缓存进度到本地联网后上传
            localStorage.setItem(this.book_info['name'], JSON.stringify({
                "index": index,
                "time": Date.now()
            }))
            return false
        }

        var data = { "name": this.book_info['name'], "author": this.book_info['author'], "durChapterIndex": index, "durChapterPos": 0, "durChapterTime": Date.now(), "durChapterTitle": this.chapter_list[index]['title'] }
        var options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        }
        fetch(`${host}/saveBookProgress`, options).then(
            resp => resp.json()
        ).then(
            data => console.log(data)
        )
    }
    openbook(i) {
        this.book_info = this.book_shelf[i]
        this.book_num = i
        this.visual()
        this.bname()
        this.author()
        this.directory()
        this.max_index = this.book_info['totalChapterNum'] // 最大章节
    }
    delete() {
        this.wrapper.innerHTML = ""
    }
    visual() {
        if (this.wrapper.style.display === 'none') {
            this.wrapper.style.display = 'grid';
        } else {
            this.wrapper.style.display = 'none';
        }
    }
    prev() {
        let backup = this.chapter_index
        if (--this.chapter_index < 0) {
            this.chapter_index = 0
            console.log("已经是第一章了哦");
        } else {
            this.openchapter(this.chapter_index).then(
                (stat) => {
                    if (stat == false) {
                        this.chapter_index = backup
                    }else{
                        window.scrollTo(0, 0)
                    }
                }
            )
        }
    }
    next() {
        let backup = this.chapter_index
        if (++this.chapter_index > this.max_index) {
            this.chapter_index = this.max_index
            console.log("已经是最后一章了哦");
        } else {
            this.openchapter(this.chapter_index).then(
                (stat) => {
                    if (stat == false) {
                        this.chapter_index = backup
                    }else{
                        window.scrollTo(0, 0)
                    }
                }
            )
        }
    }
    text(text) {
        var columns = text.split("\n")
        var bhtml = []
        columns.forEach(column => {
            let match = /src="([^"]*)"/.exec(column)
            if (match) {
                let src = `${host}/image?path=${match[1]}&url=${encodeURIComponent(this.book_info['bookUrl'])}&width=960`
                column = `<img src="${src}" />`
            }
            bhtml.push(`<p>${column}<\p>`)
        })
        var content = bhtml.join("\n")
        $("#ChapterBody").html(content)
        $(".article-desc span:nth-child(2)").text("字数:" + content.length)
    }

    bname() {
        $(".item.bold").text(this.book_info['name'])
    }

    author() {
        $(".article-desc span:nth-child(1)").text("作者:" + this.book_info['author'] || "未知")
    }

    title(title) {
        $(".article-title").text(title)
    }

    async offline() {
        var chapters = this.chapter_list
        for (let i = 0; i < chapters.length; i++) {
            let url = `${host}/getBookContent?url=${encodeURIComponent(this.book_info['bookUrl'])}&index=${i}`
            await request(url)
            console.log(`缓存进度${i + 1}/${chapters.length}`)
        }
    }
}

function InitBook() {
    window['host'] = 'http://192.168.31.112:1087'
    window.tip = new MessageTip("正在检查连接")
    tip.show()
    tip.open()
    CheckConnection().then(
        (status) => {
            if (status) {
                tip.off()
                tip.hide()
            } else {
                tip.off()
                setTimeout(() => {
                    tip.text("离线模式")
                    setTimeout(() => {
                        tip.hide()
                    }, 3000)
                }, 1000)
            }
            window['online'] = status
            console.log('连接状态:' + status)
            window.offline = new Offline(
                function () {
                    console.log("Offline初始化完成");
                    window.books = new Books()
                    books.update()
                    books.visual()
                    console.log("初始化结束");
                }
            )
        }
    )
}

$('#chapter').mouseenter(function () {
    clearTimeout(window.timeoutId);
});

$('#chapter').mouseleave(function () {
    window.timeoutId = setTimeout(function () {
        $(".chapter-box").toggleClass("hide", true)
    }, 1500);
});

$('#settingBox').mouseenter(function () {
    clearTimeout(window.timeoutId2);
});

$('#settingBox').mouseleave(function () {
    window.timeoutId2 = setTimeout(function () {
        $("#settingBox").toggleClass("hide", true)
    }, 1500);
});

$(".ctrl-btn.menu-btn").on("click", function () {
    $(".chapter-box").toggleClass("hide")
    books.chapter_list[books.chapter_index]['node'][0].scrollIntoView({
        behavior: 'smooth',
        block: 'center'
    })
})//目录

$(".ctrl-btn.setting-btn").on("click", function () {
    $("#settingBox").toggleClass("hide")
    $(".chapter-box").toggleClass("hide", true)
})//设置

$(".ctrl-btn.back-list-btn").on("click", function () {
    books.visual()
    $("#settingBox").toggleClass("hide", true)
    $(".chapter-box").toggleClass("hide", true)
})//书架

$(".ctrl-btn-prev-btn").on("click", function () {
    books.prev()
})//上一页

$(".ctrl-btn-next-btn").on("click", function () {
    books.next()
})//下一页
$(InitBook)

免费评分

参与人数 6吾爱币 +11 热心值 +6 收起 理由
cacheline + 1 + 1 我很赞同!
soughing + 1 + 1 我很赞同!
jiaokeer + 1 + 1 我很赞同!
cfx0619 + 1 + 1 谢谢@Thanks!
黑夜之天 + 1 热心回复!
爱飞的猫 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
蓝风 发表于 2024-3-2 18:46
这个很不错啊,楼主研究的比较有趣。虽然这个功能几乎没用上过呢。
3#
三滑稽甲苯 发表于 2024-3-2 19:54
4#
5iAnn2020 发表于 2024-3-2 19:57
脱机很好的。可惜小白看着代码不会用。但是也要支持一下
5#
NIEMINGYAN95188 发表于 2024-3-2 20:08
不错不错学会了吗?
6#
nonsafety 发表于 2024-3-2 22:00
谢谢分析!
7#
liu流年 发表于 2024-3-2 22:33
感谢楼主的分享
8#
sdvip168 发表于 2024-3-2 23:49
可惜我看不懂
9#
lenghanbing 发表于 2024-3-3 00:18
感谢谢谢
10#
wwwccn123 发表于 2024-3-3 08:35
小白,看不懂,但是很厉害
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 19:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表