// ==UserScript==
// @name        NGA Watcher
// @namespace   https://greatest.deepsurf.us/users/263018
// @version     1.1.0
// @author      snyssss
// @description 同步客户端关注功能
// @match       *://bbs.nga.cn/*
// @match       *://ngabbs.com/*
// @match       *://nga.178.com/*
// @grant       GM_addStyle
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_addValueChangeListener
// @grant       GM_registerMenuCommand
// @noframes
// ==/UserScript==
((ui, self) => {
  if (!ui) return;
  // 钩子
  const hookFunction = (object, functionName, callback) => {
    ((originalFunction) => {
      object[functionName] = function () {
        const returnValue = originalFunction.apply(this, arguments);
        callback.apply(this, [returnValue, originalFunction, arguments]);
        return returnValue;
      };
    })(object[functionName]);
  };
  // STYLE
  GM_addStyle(`
    .s-user-info-container:not(:hover) .ah {
        display: none !important;
    }
    .s-table {
      border: 1px solid #ead5bc;
      border-left: none;
      border-bottom: none;
      width: 99.95%;
    }
    .s-table thead {
      background-color: #591804;
      color: #fff8e7;
    }
    .s-table tbody tr {
      background-color: #fff0cd;
    }
    .s-table tbody tr:nth-of-type(odd) {
      background-color: #fff8e7;
    }
    .s-table td {
      border: 1px solid #ead5bc;
      border-top: none;
      border-right: none;
      padding: 6px;
      white-space: nowrap;
    }
    .s-table input:not([type]) {
      margin: 0;
      width: 100%;
      box-sizing: border-box;
    }
  `);
  // 用户信息
  class UserInfo {
    execute(task) {
      task().finally(() => {
        if (this.waitingQueue.length) {
          const next = this.waitingQueue.shift();
          this.execute(next);
        } else {
          this.isRunning = false;
        }
      });
    }
    enqueue(task) {
      if (this.isRunning) {
        this.waitingQueue.push(task);
      } else {
        this.isRunning = true;
        this.execute(task);
      }
    }
    rearrange() {
      if (this.data) {
        const list = Object.values(this.children);
        for (let i = 0; i < list.length; i++) {
          if (list[i].source === undefined) {
            list[i].create(this.data);
          }
          Object.entries(this.container).forEach((item) => {
            list[i].clone(this.data, item);
          });
        }
      }
    }
    reload() {
      this.enqueue(async () => {
        this.data = await new Promise((resolve) => {
          fetch(`/nuke.php?lite=js&__lib=ucp&__act=get&uid=${this.uid}`)
            .then((res) => res.blob())
            .then((blob) => {
              const reader = new FileReader();
              reader.onload = () => {
                const text = reader.result;
                const result = JSON.parse(
                  text.replace("window.script_muti_get_var_store=", "")
                );
                resolve(result.data[0]);
              };
              reader.readAsText(blob, "GBK");
            })
            .catch(() => {
              resolve();
            });
        });
        Object.values(this.children).forEach((item) => item.destroy());
        this.rearrange();
      });
    }
    constructor(id) {
      this.uid = id;
      this.waitingQueue = [];
      this.isRunning = false;
      this.container = {};
      this.children = {};
      this.reload();
    }
  }
  // 用户信息组件
  class UserInfoWidget {
    destroy() {
      if (this.source) {
        this.source = undefined;
      }
      if (this.target) {
        Object.values(this.target).forEach((item) => {
          if (item.parentNode) {
            item.parentNode.removeChild(item);
          }
        });
      }
    }
    clone(data, [argid, container]) {
      if (this.source) {
        if (this.target[argid] === undefined) {
          this.target[argid] = this.source.cloneNode(true);
          if (this.callback) {
            this.callback(data, this.target[argid]);
          }
        }
        container.appendChild(this.target[argid]);
      }
    }
    constructor(func, callback) {
      this.create = (data) => {
        this.destroy();
        this.source = func(data);
        this.target = {};
      };
      this.callback = callback;
    }
  }
  ui.sn = ui.sn || {};
  ui.sn.userInfo = ui.sn.userInfo || {};
  ((info) => {
    // 关注
    const follow = (uid) =>
      new Promise((resolve, reject) => {
        fetch(
          `/nuke.php?lite=js&__lib=follow_v2&__act=follow&id=${uid}&type=1`,
          {
            method: "post",
          }
        )
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();
            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );
              if (result.data) {
                resolve(result.data[0]);
              } else {
                reject(result.error[0]);
              }
            };
            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve();
          });
      });
    // 取消关注
    const un_follow = (uid) =>
      new Promise((resolve, reject) => {
        fetch(
          `/nuke.php?lite=js&__lib=follow_v2&__act=follow&id=${uid}&type=8`,
          {
            method: "post",
          }
        )
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();
            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );
              if (result.data) {
                resolve(result.data[0]);
              } else {
                reject(result.error[0]);
              }
            };
            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve();
          });
      });
    // 移除粉丝
    const un_follow_fans = (uid) =>
      new Promise((resolve, reject) => {
        fetch(
          `/nuke.php?lite=js&__lib=follow_v2&__act=follow&id=${uid}&type=256`,
          {
            method: "post",
          }
        )
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();
            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );
              if (result.data) {
                resolve(result.data[0]);
              } else {
                reject(result.error[0]);
              }
            };
            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve();
          });
      });
    // 获取关注列表
    const follow_list = (page) =>
      new Promise((resolve, reject) => {
        fetch(
          `/nuke.php?lite=js&__lib=follow_v2&__act=get_follow&page=${page}`,
          {
            method: "post",
          }
        )
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();
            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );
              if (result.data) {
                resolve(result.data[0]);
              } else {
                reject(result.error[0]);
              }
            };
            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve();
          });
      });
    // 获取粉丝列表
    const follow_by_list = (page) =>
      new Promise((resolve, reject) => {
        fetch(
          `/nuke.php?lite=js&__lib=follow_v2&__act=get_follow_by&page=${page}`,
          {
            method: "post",
          }
        )
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();
            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );
              if (result.data) {
                resolve(result.data[0]);
              } else {
                reject(result.error[0]);
              }
            };
            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve();
          });
      });
    // 获取关注动态
    const follow_dymanic_list = () =>
      new Promise((resolve, reject) => {
        fetch(`/nuke.php?lite=js&__lib=follow_v2&__act=get_push_list`, {
          method: "post",
        })
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();
            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );
              if (result.data) {
                resolve(result.data);
              } else {
                reject(result.error[0]);
              }
            };
            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve();
          });
      });
    // UI
    const u = (() => {
      const modules = {};
      const createView = () => {
        const tabContainer = (() => {
          const c = document.createElement("div");
          c.className = "w100";
          c.innerHTML = `
            <div class="right_" style="margin-bottom: 5px;">
                <table class="stdbtn" cellspacing="0">
                    <tbody>
                        <tr></tr>
                    </tbody>
                </table>
            </div>
            <div class="clear"></div>
            `;
          return c;
        })();
        const tabPanelContainer = (() => {
          const c = document.createElement("div");
          c.style = "width: 40vw;";
          return c;
        })();
        const content = (() => {
          const c = document.createElement("div");
          c.append(tabContainer);
          c.append(tabPanelContainer);
          return c;
        })();
        const addModule = (() => {
          const tc = tabContainer.getElementsByTagName("tr")[0];
          const cc = tabPanelContainer;
          return (module) => {
            const tabBox = document.createElement("td");
            tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
            const tab = tabBox.childNodes[0];
            const toggle = () => {
              Object.values(modules).forEach((item) => {
                if (item.tab === tab) {
                  item.tab.className = "nobr";
                  item.content.style = "display: block";
                  item.visible = true;
                } else {
                  item.tab.className = "nobr silver";
                  item.content.style = "display: none";
                  item.visible = false;
                }
              });
              module.refresh();
            };
            tc.append(tabBox);
            cc.append(module.content);
            tab.onclick = toggle;
            modules[module.name] = {
              ...module,
              tab,
              toggle,
              visible: false,
            };
            return modules[module.name];
          };
        })();
        return {
          content,
          modules,
          addModule,
        };
      };
      const refresh = () => {
        Object.values(modules)
          .find((item) => item.visible)
          ?.refresh();
      };
      return {
        createView,
        refresh,
      };
    })();
    // 我的关注
    {
      const name = "我的关注";
      const content = (() => {
        const c = document.createElement("div");
        c.style.display = "none";
        c.innerHTML = `
        <div style="max-height: 400px; overflow: auto;">
          <table class="s-table">
            <tbody></tbody>
          </table>
        </div>
        `;
        return c;
      })();
      let page = 0;
      let lastSize = -1;
      let isFetching = false;
      const box = content.querySelector("DIV");
      const list = content.querySelector("TBODY");
      const fetchData = () => {
        isFetching = true;
        follow_list(page)
          .then((res) => {
            lastSize = Object.keys(res).length;
            for (let i in res) {
              const { uid, username } = res[i];
              const name = `s-follow-${uid}`;
              if (list.querySelector(`#${name}`)) {
                continue;
              }
              const item = document.createElement("TR");
              item.id = name;
              item.innerHTML = `
              <td>
                <a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>
              </td>
              <td width="1">
                <button>移除</button>
              </td>
            `;
              const action = item.querySelector("BUTTON");
              action.onclick = () => {
                if (confirm("取消关注?")) {
                  un_follow(uid).then(() => {
                    info[uid].reload();
                    u.refresh();
                  });
                }
              };
              list.appendChild(item);
            }
          })
          .finally(() => {
            isFetching = false;
          });
      };
      box.onscroll = () => {
        if (isFetching || lastSize === 0) {
          return;
        }
        if (box.scrollHeight - box.scrollTop - box.clientHeight <= 40) {
          page = page + 1;
          fetchData();
        }
      };
      const refresh = () => {
        list.innerHTML = "";
        page = 1;
        lastSize = -1;
        fetchData();
      };
      hookFunction(u, "createView", (view) => {
        view.addModule({
          name,
          content,
          refresh,
        });
      });
    }
    // 我的粉丝
    {
      const name = "我的粉丝";
      const content = (() => {
        const c = document.createElement("div");
        c.style.display = "none";
        c.innerHTML = `
          <div style="max-height: 400px; overflow: auto;">
            <table class="s-table">
              <tbody></tbody>
            </table>
          </div>
          `;
        return c;
      })();
      let page = 0;
      let lastSize = -1;
      let isFetching = false;
      const box = content.querySelector("DIV");
      const list = content.querySelector("TBODY");
      const fetchData = () => {
        isFetching = true;
        follow_by_list(page)
          .then((res) => {
            lastSize = Object.keys(res).length;
            for (let i in res) {
              const { uid, username } = res[i];
              const name = `s-fans-${uid}`;
              if (list.querySelector(`#${name}`)) {
                continue;
              }
              const item = document.createElement("TR");
              item.id = name;
              item.innerHTML = `
                <td>
                  <a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>
                </td>
                <td width="1">
                  <button>移除</button>
                </td>
              `;
              const action = item.querySelector("BUTTON");
              action.onclick = () => {
                if (confirm("移除粉丝?")) {
                  un_follow_fans(uid).then(() => {
                    u.refresh();
                  });
                }
              };
              list.appendChild(item);
            }
          })
          .finally(() => {
            isFetching = false;
          });
      };
      box.onscroll = () => {
        if (isFetching || lastSize === 0) {
          return;
        }
        if (box.scrollHeight - box.scrollTop - box.clientHeight <= 40) {
          page = page + 1;
          fetchData();
        }
      };
      const refresh = () => {
        list.innerHTML = "";
        page = 1;
        lastSize = -1;
        fetchData();
      };
      hookFunction(u, "createView", (view) => {
        view.addModule({
          name,
          content,
          refresh,
        });
      });
    }
    // 关注动态
    {
      const name = "关注动态";
      const content = (() => {
        const c = document.createElement("div");
        c.style.display = "none";
        c.innerHTML = `
          <div style="max-height: 400px; overflow: auto;">
            <table class="s-table">
              <tbody></tbody>
            </table>
          </div>
          `;
        return c;
      })();
      let page = 0;
      let lastSize = -1;
      let isFetching = false;
      const box = content.querySelector("DIV");
      const list = content.querySelector("TBODY");
      const fetchData = () => {
        isFetching = true;
        follow_dymanic_list(page)
          .then((res) => {
            if (res[1] === res[2]) {
              lastSize = 0;
            } else {
              lastSize = -1;
            }
            return res[0];
          })
          .then((res) => {
            for (let i in res) {
              const id = res[i][0];
              const time = res[i][6];
              const summary = res[i]["summary"];
              const name = `s-follow-dymanic-${id}`;
              if (list.querySelector(`#${name}`)) {
                continue;
              }
              const parsedSummary = summary
                .replace(
                  /\[uid=(\d+)\](.+)\[\/uid\]/,
                  `<a href="/nuke.php?func=ucp&uid=$1" class="b nobr">$2</a>`
                )
                .replace(
                  /\[pid=(\d+)\](.+)\[\/pid\]/,
                  `<a href="/read.php?pid=$1" class="b nobr">回复</a>`
                )
                .replace(/\[tid=(\d+)\](.+)\[\/tid\]/, function ($0, $1, $2) {
                  let s = ui.cutstrbylen($2, 19);
                  if (s.length < $2.length) {
                    s += "...";
                  }
                  return `<a href="/read.php?tid=${$1}" class="b nobr">${s}</a>`;
                });
              const item = document.createElement("TR");
              item.id = name;
              item.innerHTML = `
                <td width="100">
                  ${ui.time2dis(time)}
                </td>
                <td>
                  ${parsedSummary}
                </td>
              `;
              list.appendChild(item);
            }
          })
          .finally(() => {
            isFetching = false;
          });
      };
      box.onscroll = () => {
        if (isFetching || lastSize === 0) {
          return;
        }
        if (box.scrollHeight - box.scrollTop - box.clientHeight <= 40) {
          page = page + 1;
          fetchData();
        }
      };
      const refresh = () => {
        list.innerHTML = "";
        page = 1;
        lastSize = -1;
        fetchData();
      };
      hookFunction(u, "createView", (view) => {
        view.addModule({
          name,
          content,
          refresh,
        });
      });
    }
    // 打开菜单
    const showMenu = (() => {
      let view, window;
      return () => {
        if (view === undefined) {
          view = u.createView();
        }
        view.modules["关注动态"].toggle();
        if (window === undefined) {
          window = ui.createCommmonWindow();
        }
        window._.addContent(null);
        window._.addTitle(`关注`);
        window._.addContent(view.content);
        window._.show();
      };
    })();
    // 增加菜单项
    if (document.querySelector(`[name="unisearchinput"]`)) {
      const anchor = document.querySelector("#mainmenu .td:last-child");
      const button = document.createElement("DIV");
      button.className = `td`;
      button.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">关注</a>`;
      button.onclick = showMenu;
      anchor.before(button);
    }
    let popover;
    const execute = (argid) => {
      const args = ui.postArg.data[argid];
      if (args.comment) return;
      const uid = +args.pAid;
      if (uid > 0) {
        if (info[uid] === undefined) {
          info[uid] = new UserInfo(uid);
        }
        if (document.contains(info[uid].container[argid]) === false) {
          info[uid].container[argid] = args.uInfoC.querySelector(
            "[name=uid]"
          ).parentNode;
        }
        info[uid].enqueue(async () => {
          args.uInfoC.className =
            args.uInfoC.className + " s-user-info-container";
          if (info[uid].children[16]) {
            info[uid].children[16].destroy();
          }
          info[uid].children[16] = new UserInfoWidget(
            (data) => {
              const value = data.follow_by_num || 0;
              const element = document.createElement("SPAN");
              if (uid === self || data.follow) {
                element.className =
                  "small_colored_text_btn stxt block_txt_c2 vertmod";
              } else {
                element.className =
                  "small_colored_text_btn stxt block_txt_c2 vertmod ah";
              }
              element.style.cursor = "default";
              element.innerHTML = `<span class="white"><span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">★</span> ${value}</span>`;
              element.style.cursor = "pointer";
              return element;
            },
            (data, element) => {
              if (!self) return;
              const handleClose = () => {
                if (popover) {
                  popover.style.display = "none";
                }
              };
              const handleSwitchFollow = () => {
                if (data.follow) {
                  if (confirm("取消关注?")) {
                    un_follow(data.uid).then(() => {
                      info[uid].reload();
                      u.refresh();
                    });
                  }
                } else {
                  follow(data.uid).then(() => {
                    info[uid].reload();
                    u.refresh();
                  });
                }
                handleClose();
              };
              element.onclick = (e) => {
                if (uid === self) {
                  showMenu();
                  return;
                }
                if (!popover) {
                  popover = document.createElement("SPAN");
                  popover.className = "urltip2 urltip3 ah";
                  popover.style = "textAlign: left; margin: 0;";
                }
                if (element.parentNode !== popover.parentNode) {
                  element.parentNode.appendChild(popover);
                }
                if (data.follow) {
                  if (popover.type !== 1) {
                    popover.type = 1;
                    popover.innerHTML = `<nobr>
                      <a href="javascript: void(0);">[已关注]</a>
                      <a href="javascript: void(0);">[关闭]</a>
                    </nobr>`;
                    const buttons = popover.getElementsByTagName("A");
                    buttons[0].onclick = handleSwitchFollow;
                    buttons[1].onclick = handleClose;
                  }
                } else {
                  if (popover.type !== 2) {
                    popover.type = 2;
                    popover.innerHTML = `<nobr>
                        <a href="javascript: void(0);">[关注]</a>
                        <a href="javascript: void(0);">[关闭]</a>
                    </nobr>`;
                    const buttons = popover.getElementsByTagName("A");
                    buttons[0].onclick = handleSwitchFollow;
                    buttons[1].onclick = handleClose;
                  }
                }
                popover.style.left = `${e.pageX}px`;
                popover.style.top = `${e.pageY}px`;
                popover.style.display = "block";
              };
            }
          );
          info[uid].rearrange();
        });
      }
    };
    if (ui.postArg) {
      Object.keys(ui.postArg.data).forEach((i) => execute(i));
    }
    let initialized = false;
    hookFunction(ui, "eval", () => {
      if (initialized) return;
      if (ui.postDisp) {
        hookFunction(
          ui,
          "postDisp",
          (returnValue, originalFunction, arguments) => execute(arguments[0])
        );
        initialized = true;
      }
    });
  })(ui.sn.userInfo);
})(commonui, __CURRENT_UID);