/*
 * 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中
 * Copyright 2024 满月叶
 * GitHub: https://github.com/MoonLeeeaf/LingChair-Web-Client
 * 本项目使用 Apache 2.0 协议开源
 * 
 * Copyright 2024 MoonLeeeaf
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const UrlArgs = new URL(location.href).searchParams

// https://www.ruanyifeng.com/blog/2021/09/detecting-mobile-browser.html
function isMobile() { 
  return ('ontouchstart' in document.documentElement); 
}

function setOnRightClick(e, cb) {
    if (!(e instanceof jQuery))
        e = $(e)

    let longPressTimer
    if (!cb) throw new Error("定义回调!!!!")
    e.on('contextmenu', function (e) {
        e.preventDefault() // 阻止默认右键菜单
        cb()
    })

    e.on('mousedown', function () {
        longPressTimer = setTimeout(function () {
            cb()
        }, 1000)
    })

    e.on('mouseup', function () {
        clearTimeout(longPressTimer)
    });
}

if (UrlArgs.get("debug")) {
    let script = document.createElement('script')
    script.src = "//cdn.jsdelivr.net/npm/eruda"
    document.body.appendChild(script)
    script.onload = () => eruda.init()
}

// 经常会因为这个指定ID为位置导致一些莫名BUG
if (location.href.includes("#")) location.replace(location.href.substring(0, location.href.indexOf("#")))

const mdui_snackbar = mdui.snackbar
mdui.snackbar = (m) => {
    let t = m
    if (m instanceof Object)
        t = JSON.stringify(m)
    mdui_snackbar(t)
}

const checkEmpty = (i) => {
    if (i instanceof Array) {
        for (let k of i) {
            if (checkEmpty(k)) return true
        }
    }

    return (i == null) || ("" === i) || (0 === i)
}

function escapeHTML(str) {
    return str.replace(/[<>&"']/g, function (match) {
        switch (match) {
            case '<':
                return '&lt;'
            case '>':
                return '&gt;'
            case '&':
                return '&amp;'
            case '"':
                return '&quot;'
            case "'":
                return '&#39;'
            default:
                return match
        }
    })
}

class NData {
    static mount(node) {
        // 便捷获得指定组件
        let es = node.querySelectorAll("[n-id]")
        let ls = {}
        es.forEach((i) => ls[$(i).attr("n-id")] = $(i))

        // input 组件与 localStorage 绑定
        es = node.querySelectorAll("[n-input-ls]")
        es.forEach((e) => {
            let j = $(e)
            j.val(localStorage.getItem(j.attr("n-input-ls")))
            j.blur(() => localStorage.setItem(j.attr("n-input-ls"), j.val()))
        })
        return ls
    }
}

// 快捷获取指定视图
let viewBinding = NData.mount($("#app").get(0))

$.ajax({
    url: "res/config.json",
    dataType: "json",
    success: (c) => {
        viewBinding.appTitle.text(c.appTitle)
        if (!c.canChangeServer) {
            viewBinding.dialogSignInServerLabel.hide()
            viewBinding.drawerChangeServer.hide()
        }
    },
})

/* // Toolbar 快捷按钮绑定
viewBinding.contactsRefresh.hide()
viewBinding.contactsAdd.hide()
viewBinding.tabChatList.on("show.mdui.tab", () => {
    viewBinding.contactsRefresh.hide()
    viewBinding.contactsAdd.hide()
})
viewBinding.tabContacts.on("show.mdui.tab", () => {
    viewBinding.contactsRefresh.show()
    viewBinding.contactsAdd.show()
})
viewBinding.tabChatSeesion.on("show.mdui.tab", () => {
    viewBinding.contactsRefresh.hide()
    viewBinding.contactsAdd.hide()
}) */

/* viewBinding.tabChatSeesion.hide() */

// 关于页面
viewBinding.menuAbout.click(() => mdui.alert('这是一个开源项目<br/>作者: MoonLeeeaf<br/>欢迎访问我们的<a class="mdui-text-color-theme-accent" href="https://github.com/LingChair/LingChair">项目主页</a>', '关于 铃之椅', () => { }, { confirmText: "关闭" }))

viewBinding.drawerChangeServer.click(() => {
    mdui.prompt('输入服务器地址...(为空则使用当前页面地址)', (value) => {
        localStorage.server = value
        mdui.snackbar("更新成功, 刷新页面生效")
    }, () => { }, {
        confirmText: "确定",
        cancelText: "取消"
    })
})

viewBinding.drawerSignOut.click(() => {
    mdui.confirm('确定要登出账号吗', () => {
        User.signOutAndReload()
    }, () => { }, {
        confirmText: "确定",
        cancelText: "取消"
    })
})

viewBinding.sendMsg.click((a) => {
    let text = viewBinding.inputMsg.val()
    if (text.trim() !== "")
        ChatMsgAdapter.send(text)
})

viewBinding.inputMsg.keydown((e) => {
    if (e.ctrlKey && e.keyCode === 13)
        viewBinding.sendMsg.click()
})

viewBinding.dialogSignInPasswd.keydown((e) => {
    if (e.keyCode === 13)
        viewBinding.dialogSignInEnter.click()
})

viewBinding.switchNotifications.click((a) => {
    if ((localStorage.useNotifications == "true" || localStorage.useNotifications != null) && localStorage.useNotifications != "false") {
        localStorage.useNotifications = "false"
        viewBinding.switchNotificationsIcon.text("notifications_off")
    } else {
        localStorage.useNotifications = "true"
        viewBinding.switchNotificationsIcon.text("notifications")
    }
})
if (localStorage.useNotifications == "true")
    viewBinding.switchNotificationsIcon.text("notifications")

// https://www.runoob.com/w3cnote/javascript-copy-clipboard.html
function copyText(t) {
    let btn = viewBinding.textCopierBtn
    btn.attr("data-clipboard-text", t)
    new ClipboardJS(btn.get(0)).on('success', (e) => {
        e.clearSelection()
    })
    btn.click()
}

// https://zhuanlan.zhihu.com/p/162910462
Date.prototype.format = function (tms, format) {
    let tmd = new Date(tms)
    /*
     * 例子: format="YYYY-MM-dd hh:mm:ss";
     */
    var o = {
        "M+": tmd.getMonth() + 1, // month
        "d+": tmd.getDate(), // day
        "h+": tmd.getHours(), // hour
        "m+": tmd.getMinutes(), // minute
        "s+": tmd.getSeconds(), // second
        "q+": Math.floor((tmd.getMonth() + 3) / 3), // quarter
        "S": tmd.getMilliseconds()
        // millisecond
    }
    if (/(y+)/.test(format)) {
        format = format.replace(RegExp.$1, (tmd.getFullYear() + "")
            .substr(4 - RegExp.$1.length));
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(format)) {
            format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k]
                : ("00" + o[k]).substr(("" + o[k]).length));
        }
    }
    return format;
}

// new mdui.Drawer('#main-drawer').close()

class NickCache {
    static data = {}
    static async getNick(name) {
        return await new Promise((res, rej) => {
            // 这个this别摆着不放啊 不然两下就会去世
            let nick = this.data[name]
            if (nick == null)
                client.emit("user.getNick", { name: localStorage.userName }, (re) => {
                    let nk = re.data != null ? re.data.nick : name
                    if (nk == null) nk = name
                    this.data[name] = nk
                    res(nk)
                })
            else
                res(nick)
        })
    }
}

// 既然已经有 Notification 了, 那用回中文也不过分吧 :)
class 通知 {
    constructor() {
        this.args = {}
        this.title = ""
    }
    static checkAvailable() {
        return ("Notification" in window)
    }
    static async request() {
        if (!this.checkAvailable()) return false
        return (await Notification.requestPermission())
    }
    setId(id) {
        this.args.tag = id
        return this
    }
    setTitle(t) {
        this.title = t
        return this
    }
    setMessage(m) {
        this.args.body = m
        return this
    }
    setIcon(i) {
        this.args.icon = i
        return this
    }
    setImage(i) {
        this.args.image = i
        return this
    }
    setData(data) {
        this.args.data = data
    }
    show(onclick/*, onclose*/) {
        if (!通知.checkAvailable()) return
        if (localStorage.useNotifications !== "true") return
        let n = new Notification(this.title, this.args)
        n.onclick = onclick == null ? () => n.close() : (n) => onclick(n)
        // n.onclose = onclose
        // n.close()
        return n
    }
}

class ContactsList {
    static async reloadList() {
        client.emit("user.getFriends", {
            name: localStorage.userName,
            accessToken: await User.getAccessToken(),
        }, async (re) => {
            if (re.code !== 0)
                return mdui.snackbar(re.msg)

            viewBinding.contactsList.empty()
            let ls = re.data.friends
            for (let index in ls) {
                let name = ls[index]
                let dick = await NickCache.getNick(name)
                /*client.emit("user.getNick", { name: localStorage.userName }, (re) => {
                    let nick = re.data == null ? re.data.nick : null
                    let name = ls[index]*/
                $($.parseHTML(`<li class="mdui-list-item mdui-ripple"><div class="mdui-list-item-avatar"><img src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" /></div><div class="mdui-list-item-content">` + dick + `</div></li>`)).appendTo(viewBinding.contactsList).click(() => {
                    ChatMsgAdapter.switchTo(name, "single")
                })
                //})
            }

        })
    }
    // 添加联系人，好友或者群聊
    static add(name, type) {
        if (type == "single") {

        }
    }
    static openAddDialog() {
        new mdui.Dialog(viewBinding.dialogNewContact.get(0)).open()
    }
}

// 第一次写前端的消息加载, 代码很乱, 还请原谅~

// v0.7.0 大改UI 畏惧了 太庞大了

class ChatPage {
    static cached = {}
    constructor(name, type) {

    }
    static switchTo(name, type) {
        if (!this.cached[name])
            this.cached[name] = new ChatPage(name, type)
    }
}

class ChatMsgAdapter {
    static type
    static target
    // static msgList
    static minMsgId
    static time
    static bbn
    static resizeDick
    // 切换聊天对象
    static async switchTo(name, type) {
        viewBinding.tabChatSeesion.show()
        viewBinding.tabChatSeesion.text(await NickCache.getNick(name))
        viewBinding.tabChatSeesion.get(0).click()

        this.type = type
        this.target = name
        // this.msgList = []
        this.minMsgId = null

        viewBinding.pageChatSeesion.empty()

        await this.loadMore()
        this.scrollToBottom()
    }
    // 发送消息
    static async send(msg) {
        client.emit("user.sendSingleMsg", {
            name: localStorage.userName,
            target: this.target,
            msg: msg,
            accessToken: await User.getAccessToken(),
        }, async (re) => {
            if (re.code !== 0)
                return mdui.snackbar(re.msg)

            viewBinding.inputMsg.val("")

            // 微机课闲的没事干玩玩 发现私聊会多发一个(一个是本地的, 另一个是发送成功的) 选择一个关掉就好了
            // 这里我选择服务端不发送回调, 不然多设备同步会吵死
            // 错了 应该是客户端少发条才对 不然不能多设备同步
            if (ChatMsgAdapter.target !== localStorage.userName && ChatMsgAdapter.type === "single") {
                let i = ChatMsgAdapter.isAtBottom()
                await ChatMsgAdapter.addMsg(localStorage.userName, msg, re.data.time, re.data.msgid)
                if (i) ChatMsgAdapter.scrollToBottom()
            }
        })
    }
    static async getHistroy(start, limit) {
        return new Promise(async (res, rej) => {
            client.emit("user.getSingleChatHistroy", {
                name: localStorage.userName,
                target: this.target,
                limit: limit,
                accessToken: await User.getAccessToken(),
                startId: start,
            }, (re) => {
                if (re.code !== 0)
                    return mdui.snackbar(re.msg)
                res(re.data.histroy)
            })
        })
    }
    static async loadMore(limit) {
        let histroy = await this.getHistroy(this.minMsgId, limit == null ? 13 : limit)

        if (histroy.length == 0)
            return mdui.snackbar("已经加载完了~")

        let re = this.minMsgId != null
        this.minMsgId = histroy[0].msgid - 1
        let sc = 0
        if (re) histroy = histroy.reverse()
        for (let index in histroy) {
            let i = histroy[index]
            let e = await this.addMsg(i.name, i.msg, i.time, re, i.msgid)
            // 因为某些因素直接DEBUG到吐血 断点继续都不报错 原因不明
            sc = sc + (e == null ? 25 : e.get(0).offsetTop)
        }
        window.scrollBy({
            top: sc,
            behavior: 'smooth'
        })
    }
    static addSystemMsg(m, re) {
        let e
        if (re)
            // 加到头部
            e = $($.parseHTML(m)).prependTo(viewBinding.pageChatSeesion)
        else
            // 加到尾部
            e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion)
        return e
    }
    static isAtBottom() {
        let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect()
        return (elementRect.bottom <= window.innerHeight)
    }
    // 添加消息 返回消息的JQ对象
    // name: 用户id  m: 消息  t: 时间戳  re: 默认加到尾部  msgid: 消息id
    static async addMsg(name, m, t, re, msgid) {

        let nick = await NickCache.getNick(name) // re.data == null ? name : re.data.nick

        let msg = escapeHTML(m)

        let temp
        if (name === localStorage.userName)
            temp = `<div class="chat-message-right">
                <div class="message-content-with-nickname-right">
                <span class="nickname">` + nick + `</span>
                <div class="message-content mdui-card" id="msgid_` + msgid + `">
                <span id="msg-content">` + msg + `</span>
                </div>
                </div>
                <img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" />
                </div>`
        else
            temp = `<div class="chat-message-left">
                <img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" />
                <div class="message-content-with-nickname-left">
                <span class="nickname">` + nick + `</span>
                <div class="message-content mdui-card" id="msgid_` + msgid + `">
                <span id="msg-content">` + msg + `</span>
                </div>
                </div>
                </div>`

        let bn = new Date(t).getMinutes()
        let e
        if (re) {
            this.addSystemMsg(temp, re)
            if (this.bbn != bn) {
                e = this.addSystemMsg(`<div class="mdui-center">` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `</div>`, re)
                this.time = bn
            }
        } else {
            if (this.bbn != bn) {
                e = this.addSystemMsg(`<div class="mdui-center">` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `</div>`, re)
                this.time = bn
            }
            this.addSystemMsg(temp, re)
        }

        this.bbn = new Date(t).getMinutes()

        return e
    }
    // 添加消息记录  作用在 UI 和 msgList
    /* static async addMsgLocal(name, m, t, msgid) {
        this.msgList.push({
            name: name,
            msg: m,
            msgid: msgid,
        })

        this.addMsg(name, m, t)
    } */
    // 从服务器加载一些聊天记录, limit默认=13
    static async loadMsgs(limit) {
        let histroy = await this.getHistroy(this.msgList[0] == null ? null : this.msgList[0].msgid - 1, limit == null ? 13 : limit)
        this.msgList = histroy
    }
    /* static async loadMsgsFromList(lst) {
        for (let index in lst) {
            let i = lst[index]
            await this.addMsg(i.name, i.msg, i.time)
        }
    } */
    static scrollToBottom() {
        // 吐了啊 原来这样就行了 我何必在子element去整啊
        viewBinding.chatPager.get(0).scrollBy({
            top: 1145141919810,
            behavior: 'smooth' 
        })
    }
    // 从本地加载
    /*static loadMsgsFromLocal(target) {
        let data = localStorage["chat_msg_" + target]
        if (data == null || data === "[]")
            return []

        return JSON.parse(data)
    }
    // 把当前聊天记录储存到本地
    static saveToLocal() {
        localStorage["chat_msg_" + this.target] = JSON.stringify(this.msgList)
    }*/
    // 自动调整使输入框置底 CSS真tm靠不住啊
    static initInputResizer() {
        // 实验表面移动端切出输入法时会触发1-2次resize事件
        // 可以利用这个特性来实现自动滚动文本
        let resize = () => {
            viewBinding.pageChatSeesion.height(window.innerHeight - viewBinding.inputToolbar.height() - $("header.mdui-appbar").height() - viewBinding.chatTab.height() - 50)
            let ledi = this.resizeDick - window.innerHeight
            if (isMobile()) viewBinding.chatPager.get(0).scrollBy({
                // 5.19晚10：56分调配出来的秘方
                // < 0 为窗口变大
                // cnm的，调试十万次就你tm检测不到底是吧，就你语法天天错误是吧
                // 欺负我现在用不了电脑
                top: -(ledi) * ( (ledi < 0 && this.isAtBottom()) ? 6 : -1 ), // (ledi < 0 ? 6 : 6),
                behavior: 'smooth'
            })
            this.resizeDick = window.innerHeight
        }
        window.addEventListener("resize", resize)
        resize()
    }
    // 为消息设置长按/右键事件
    static initMsgElementEvents() {
        let listeners = {}
        let menu
        let callback = (e) => {
            if (menu) menu.close()
            // 从 span 切到 div
            if (e.get(0).tagName.toLowerCase() != "div") e = $(e.get(0).parentNode)
            // 从 消息框 切到 更上层
            e = $(e.get(0).parentNode)
            let menuHtml = $.parseHTML(`<ul class="mdui-menu menu-on-message">
            <li class="mdui-menu-item">
              <a onclick="copyText(\`` + e.find("#msg-content").text() + `\`)" class="mdui-ripple">复制</a>
            </li>
            <li class="mdui-menu-item">
              <a onclick="mdui.alert('未制作功能', '提示', () => { }, { confirmText: '关闭' })" class="mdui-ripple">转发</a>
            </li>
            </ul>`)
            let $menu = $(menuHtml)
            e.before($menu)
            menu = new mdui.Menu(e.get(0), menuHtml, {
                position: "bottom",
                align: "right",
                // covered: true,
            })
            $menu.on('closed.mdui.menu', () => {
                $(menuHtml).remove()
            })
            menu.open()
        }
        viewBinding.pageChatSeesion.on('contextmenu mousedown mouseup', '.message-content', (e) => {
            let eventType = e.type
            let self = $(e.target)

            // 根据事件类型执行不同操作
            switch (eventType) {
                case 'contextmenu':
                    e.preventDefault() // 阻止默认行为
                    callback(self)
                    break
                case 'mousedown':
                    listeners[self + ""] = setTimeout(() => {
                        callback(self)
                    }, 300) // 300颗够吗 应该够吧
                    break
                case 'mouseup':
                    clearTimeout(listeners[self + ""])
                    listeners[self + ""] = null
                    break
            }
        })
    }
}

class Hash {
    static md5(data) {
        return CryptoJS.MD5(data).toString(CryptoJS.enc.Base64)
    }
    static sha256(data) {
        return CryptoJS.SHA256(data).toString(CryptoJS.enc.Base64)
    }
}

class User {
    static myAccessToken
    // 登录账号  通过回调函数返回刷新令牌
    static signIn(name, passwd, cb) {
        client.emit("user.signIn", {
            name: name,
            passwd: Hash.sha256(passwd) + Hash.md5(passwd),
        }, (re) => {
            if (re.code !== 0)
                return mdui.snackbar(re.msg)

            cb(re)
        })
    }
    static signUp(name, passwd, cb) {
        client.emit("user.signUp", {
            name: name,
            passwd: Hash.sha256(passwd) + Hash.md5(passwd),
        }, (re) => {
            if (re.code !== 0)
                return mdui.snackbar(re.msg)

            cb(re)
        })
    }
    // 为登录对话框编写的
    static signInWithDialog(name, passwd) {
        this.signIn(name, passwd, (re) => {
            localStorage.refreshToken = re.data.refreshToken
            localStorage.isSignIn = true

            location.reload()
        })
    }
    static async setNick(nick, cb) {
        client.emit("user.setNick", {
            name: localStorage.userName,
            accessToken: await this.getAccessToken(),
            nick: nick,
        }, (re) => {
            if (re.code !== 0)
                return mdui.snackbar(re.msg)
            if (cb) cb()
        })
    }
    // 获取头像链接
    static getUserHeadUrl(name) {
        return client.io.uri + "/users_head/" + name + ".png"
    }
    static async getAccessToken(er) {
        if (this.myAccessToken == null)
            this.myAccessToken = await new Promise((res) => {
                client.emit("user.getAccessToken", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (r) => {
                    if (r.data != null) res(r.data.accessToken)
                    if (er != null) er(r.msg)
                })
            })
        return this.myAccessToken
    }
    static uploadHeadImage() {
        viewBinding.uploadHeadImage.click()
    }
    static async uploadHeadImageCallback(self) {
        let img = self.files[0]
        client.emit("user.setHeadImage", {
            name: localStorage.userName,
            accessToken: await User.getAccessToken(),
            headImage: img,
        }, (re) => mdui.snackbar(re.msg))
    }
    static auth() {
        client.emit("user.auth", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (re) => {
            if (re.code !== 0) {
                console.error(re)
                if (!re.invalid)
                    return mdui.snackbar("验证用户失败！")

                mdui.alert("账号刷新令牌已过期, 请重新登录哦", "提示", () => User.signOutAndReload(), {
                    confirmText: "确定",
                    closeOnConfirm: false,
                    closeOnEsc: false,
                    modal: true,
                })
            }
        })
    }
    static signOutAndReload() {
        localStorage.refreshToken = ""
        localStorage.isSignIn = false

        setTimeout(() => location.reload(), 300)
    }
    static registerCallback() {
        client.on("msg.receive", async (a) => {
            if (checkEmpty([a.target, a.msg, a.type]))
                return

            if ((ChatMsgAdapter.target === a.target) && (ChatMsgAdapter.type === a.type)) {
                let i = ChatMsgAdapter.isAtBottom()
                await ChatMsgAdapter.addMsg(a.target, a.msg.msg, a.msg.time)
                if (i) ChatMsgAdapter.scrollToBottom()
            }

            let n = new 通知().setTitle("新消息 - " + await NickCache.getNick(a.target)).setMessage(a.msg.msg).setIcon(User.getUserHeadUrl(a.target)).show(async () => {
                await ChatMsgAdapter.switchTo(a.target, a.type)
                location.replace("#msgid_" + a.msg.msgid)
                n.close()
            })
        })
    }
    static async openProfileDialog(name) {
        viewBinding.dialogProfileHead.attr("src", User.getUserHeadUrl(name))
        viewBinding.dialogProfileNick.text(await NickCache.getNick(name))
        new mdui.Dialog(viewBinding.dialogProfile).open()
    }
}

// 没有刷新令牌需要重新登录 或者初始化
if (!localStorage.refreshToken || localStorage.refreshToken === "")
    localStorage.isSignIn = false

let client
function setUpClient(server) {
    if (server && server !== "")
        client = new io(server, {
            auth: {
                name: localStorage.isSignIn === "false" ? null : localStorage.userName
            }
        })
    else
        client = new io({
            auth: {
                name: localStorage.isSignIn === "false" ? null : localStorage.userName
            }
        })

    client.on("connect", () => {
        User.auth()
    })
}
if (!localStorage.server || localStorage.server === "")
    setUpClient()
else
    setUpClient(localStorage.server)

// 登录到账号
let dialogSignIn
// 谨防 localStorage 字符串数据大坑
if (localStorage.isSignIn == "false")
    dialogSignIn = new mdui.Dialog(viewBinding.dialogSignIn.get(0), {
        modal: true,
        closeOnEsc: false,
        history: false,
    }).open()
else {
    (async () => viewBinding.userNick.text(await NickCache.getNick(localStorage.userName)))()
    let hello
    let nowHour = new Date().getHours()
    if (nowHour >= 6 && nowHour <= 11) hello = "早安"
    else if (nowHour == 12) hello = "中午好"
    else if (nowHour >= 13 && nowHour <= 18) hello = "下午好"
    else if (nowHour >= 19 && nowHour < 22) hello = "晚上好"
    else hello = "晚安"
    viewBinding.helloText.text(hello)

    viewBinding.userHead.attr("src", User.getUserHeadUrl(localStorage.userName))

    ContactsList.reloadList()

    User.registerCallback()
}

// 感谢AI的力量
Stickyfill.add($("*").filter((a, b) => $(b).css('position') === 'sticky'))

ChatMsgAdapter.initMsgElementEvents()

ChatMsgAdapter.initInputResizer()

function refreshAll() {
    ContactsList.reloadList()
    delete NickCache.data
    NickCache.data = {}
}
