Greasy Fork is available in English.

哔哩哔哩新版首页排版调整和去广告(bilibili)

对新版B站首页的每行显示的视频数量进行调整, 同时删除所有广告, 并可设置屏蔽内容 (大尺寸屏幕每行将显示更多的视频)

As of 2023-11-11. See the latest version.

// ==UserScript==
// @name          哔哩哔哩新版首页排版调整和去广告(bilibili)
// @namespace     http://tampermonkey.net/
// @version       1.4.5
// @author        Ling2Ling4
// @description   对新版B站首页的每行显示的视频数量进行调整, 同时删除所有广告, 并可设置屏蔽内容 (大尺寸屏幕每行将显示更多的视频)
// @license MIT
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAbO0lEQVRogaV6ebClR3Xf75zu/r7vrm+fXctoJM2MGBZJSBaOAMeE1dhACoiNbSHLBrtcxEk5TtnBJnZsjKNyABcBl8ExcWKDMYltyhCzWCkwAu1IIMmj0TYjadY382beu9u3dfc5+ePe9+YtMiRx/3O/2326+2x9uvv0j87/5fvapx4yK1UZrAtVEkZibG7bDV8QFCAijIusfqnq+MOsNqkKFCAQMb5nUdpUIZsrtvQADV2r5fsuVIHbtXMJ+kiTO/LdpL//5jgULUT0nItKvg32aock09DV7pN54/iDQCBAARVMGF+bSrdwu4E7ujje5N/4V9dV6ToZdW12DkZAYpUcqCbKIQncrBVKiaCNnsG0KlSU1RIykAEEKpNZCETZRJw1LknWswMAsll5W2yioM1CrhNl8k26tUHHLQQFGtAGUIFX6FWvvs6ERhrqSTfVcWc1487jDgR9HubGBKujA0Dc7EHkwkU1j7UbSTfbbUsxMiGmVc7jBkkAkIdpyJCw+1qowrlJvYy7EqIDDMhOXIUibLlqjVU21WwYc5MmJ7wQiDa4VtQ1JjbTr1GZLVVxrdNqn9hBHFpOpkABLLo2PRGrmOAjxmsTrAHqNUlXNaKrI68pfGIL9pv5l4sOvjpxura2nseXJqRx3foYEyc6/tS1ShpyKK2UOayiDusk1sgU2wmMAQxiFVEhH6GU1Z5rg2/gQAFJNhpBAb8lLpVbfXFzUbOlas0CE60CaERY+7svawcjAqermnKpHVTyyW+vLJZIWCCYivXrr7/6YBZVZB3L42VxMVQRkAltUCuhJtps+Yv+tNnz1miMblxb6xfgKlHCeUja9pe+X1BMgStAQTTxS2uubLd+6o7aaT7Q9BdfcNlvv4zhw6pmdBLuSDd7/fOo7nuW53MkWltdW1qJV+dMQbmN6o0WCNmahcb9tzUEZT8kDQhPtaZgK/gcbNbNqKCtUfP/gmHaQnRxG8FEO7yqI9kqXrzYjdR+5WSzXVvGSAGoiqph5FX4wH0n0F4oSeDkk48c2bZt5/4kigRdx4CubWT/mLLe6S66Eq9OsWVn1PFORKWZDcpkrriKxIpmE1pVIpKgSFqUmqzsh6T25QziyJLdMvk/GEa+S7kYfESJiYgFk8BKq+6h67biNZlWBZjEvCwEi9wKLjcIYkoQgXgioXMUbFb0XGhbWVa0Q9IMGG2MQrrR9uPv7x1hoAoiKIGxukWsY3Oy5NfHLlqnqbWJaJQ0gWCdjIyU4tE0NAmTpKpllIG6bJDsIFaNNXiI6KAKiowIxfgspwQFTfyAhFREFSKTxUcEUjIWazu2AuxUKxbfSDo+hsCqqkqMqCgrEAAGWyQpQVRrVBVgIQpnKHEAaRSUFbAMIlsbQYjvvW7X2y5trEhsqEg2tNnM7z6w+JcPC3WWlDLSSkdxFpwk/rwvvG1b17B1zZ7K1CtbxAYDMLmN5JrTqXOZFSUWzoKOLqysaFQAKkJslWay6pH3/tMr7nis+cDxs/VMkoSiNs3LYd9yzQ6kQ7WNZ5ZHf/NcVZM9mBU/uH+6svNthCdWhnc816+5cXXLvX5/k7WuzLQFKCN+y8LCS9IKcIgBiUPWuHnXtv/x2HEjKYWqVZ189wsPvvXg1IwbPdB3v/nN3uO9aE0obUfIk4yM1JaZKMZmN+k0brju+plWhhDYuGF/8DdfviOA1TBxVIpJ9ehHXn3dO2/Y/bmnHsobRZZzdE1Uw1fvnf7Qa7ah7MP64z69+8+PL9bb3rx31wdumkHI0SifLPbe/JmHz+blv7h04TdfMY47wlAoyYoZwAxHZhRMUXCOOGj0l0ExWkNh8N6XXfOBl1/xfR25msM7tudf/OHWteZoyV2xljQaBGWpiStxXiWE4rnTx/uRSrXwFXkBGGzGnknF0u/+6E3vuuESt3QykIdjI1ZoCkhJSUdVzKHD1Fcz7XIXirbPgqAXcoOBpqWDyWDTtGqiyJY5qUKPARAo9RYhcZW1tXM+Q+0MBJ6AsKeb3Xr5Pjc4htEiQggVbHNq7769iB4YWRFFGl1HXFcKPZDEF3W0Pvvc08fP5KblyZWswQBOSfrp4NkP/NTr/uV1V2PlRG1i4pvwrVGWGunDL7copyyYxJOrWmlo4gKqM10WTtmmFWzVNGUSlhGWW65EGmbqLK3bdryuIhOIIrMIhBzUEacwBerYapmmEByqRuOZ0P1qsfBgfcnRzhTincSO1KqmZIz2T900l330bS8v4uhDX7znW6dbz8Fce+mskgARoWwMznzoltff+qI5evoZCaGy7KJDqJD4ICkau+5clr8+iZQyy/Y7y+eP2Aoz2+880frawqzQEqetB872F0MXNv3bC+WhxcbIVjY4CxBUU8tw3IgBjMQojLGuhWy5WdLS0uhI7Xe46S/3unfJ7pOuta0RquVnwJmSVfUKo4PzNy2EP7715v3lIswovOqFP/u/jp18PO/k87M7L4Ovk+L8B255y89cu2CO3K+lZfUucUIRVNlQNetBv9X91gpu/cxD3sWamjWlDTvTxehrJ4d3njwWiRNva65hk4Tlb07p1z5zSmyDJbWAVqKfO10wzYziydKi4xOThM+dPIswzDvbQ19vu//kNTde66iRt7J2M33q6ScfP3Ya1hgB2VqDP7S9/afvfu2+uIwLOVrui4eXnlqqG0nvkSf8XMi6o7Mffscr33n9XHzqiBmkkBqkbInhoTFQd+TYVEVMaLkz66IBR9iGhmEtwib1NC1kEu98ow9UJqhx3WBTKGomwhXfBxbQSlZKaROExEiAG2alrbL5aBKjdazrqam5Pft2WUfLS+dOHT0BsYaNulCF1vVzo8+865X7khSnn6UE7/368u88sIisyUmVhGCH527/iR9517V74rOP0LCwFTMqolg003/2xQt3LQ6QdW0QsZWQRxUABisJa6sLtogVhhdANUwCsOMFlrSiAu45VB0EtQCglmi3D4NMkvkw3StGg4X2qO2SKiGpfVpzakYr+aMPP4xQkga2GdiB2z70rp/q/9WtN10iRXEuNEz7V7753O0PLGVWvTsf/VRd93//tlffcmgPPXXEDUcKG8mpeFKBXNzcxBpRPWjs2w/ta1NomfRwb+m/PdMbmJkXtbO3vfSg1UFmsu+cq/7subxi+9LUvvWa/eSbcXy8IeI0H/7YlY23vnjnTu08caH+jYfuP5LPEI3vTiRQZkPUcpSJicEpmOOg/8JtyZ+/6xWXaO7Prpjm9AfufO72e0+hNePdMhUNQv6HP3Hzrddsi08dNr0BgoDHKQEGWMb3D1KoUrQQesWO+d+4cR/KZxHck9nez595YNAfvv7K7b92aDfqPtg8dVn3r8/dVVXFD12+75evmUE5QkoWRFoM33HZzIdvPtAtnkC8cO1ll+ydu/6tX3jkTIw+aZC3pDHYkkAkqSCFrdFbvHGu+6mf/oF9xuTnapNNf/Dr337ffUNqJWTL6OcRBn/09htuO5jIsdNmGBFtZFbAxKhkQELjK6wCQCKxsETqUS326HSL2qZydQI02Jk+8hMSJDQiBWoggmLUAj4EX5F4Bjhl/bGrtnV7IymaJaZxfnRjo/HaffNBenBso00ExAGmBFVgxmBww7T57z/1Q1dyrosnmig/fOfR33ggl/kWW2c9uLjwX976ktsOJcXxIYYkwQZOS2MqUopKCigxpwIGMSFEN4KrrDFAcypO27ht1rez2iGaJjPSBmfTCZIWNygwhA0cTNua7cZss7ZusVluaQb13lQBTqIyqllmRKBW70Igyco0Gk5cUdX9A3PtP77tNfv5Ak4PqEXvu/f4++8/zs3EeYiAivIjP/rinz7Uqc6cy+p64Mh4yergXFUZJ0gNrcBN3z2cP1GfQ9ZIwsgbhspdxYW/PjE/U4RRZ/mRggZFDaU7F8OhRXFVLzP+/tHp5SBQ+40Li//7eFrFCzZY6ux8zcA+/Z6XXv6f918l55+qGlnDdAfcfOWXjjw0rIkyo4NgQ1abmBlfN6/trHz6llceSJo4dwpN/PrdK7/5wCkkjpOaKzJFeP+tV/ybfVeXJ0ZZkG9Gf+fZ4idneDvOa8ySkpH0kc1+wc99eshffPD+/oUB03RSN0Oa1pqjWoZRpA3kYpNONEZ1CB2AAuBQ2SzpCHONClWJtkcIpt61ux06jyyd6tjkmpk9jdh9Eq1fvetbX1r0aKSNumZIcDaaZvTxxZ3R//zx66928MtimH7tobPvv/dU00VJcwnTUg4++rbrfmH/3vzsmWa/ODzi316efyrOPqeDg+3u7DAKh9BIvlLM/8nIPDuzfenYKe0XPk3halBQU6CVI/EsnMBK6zxnZ0kjUaaJgQU7gEvNSrWV5ZBoW8nSrr2HTskOgirOXTGtsymfuhDP9hJqG891M9dgs7qVoVe+eCb5i1uu3ed87A3rRvf37jr23ntPIWsbc86Fdin08TcdeveBufpUSKqVh8vqV6v5nuxObRwgvrFY/FedXtLGl8vuJ4ZTdeJTbt399cfP986ivWSjsG9GRQw5JCHf1cw3jWn6ONBQ8TjbMIWYd+wgOCliE2UbMYEp7KdffuPb77n3bDllsHDm1Ohoa4CslSazXJ/zaYzOirHon7uxaz91yyv2OpMvj0yj89H7Dr/vvhVKLLiIsiPG/A9/+CU/c03iT15I6uaRwvxa2T3Fl83SuQq+5S+5U8qkGhxIpj67Ys902tvrYbOOSh42UKQQFhDCCzq47UVXzWlPmu7hpeoPHu8tNTs3u/zn9l3CklTT9T2nyz/5e1t6832z8aev2Z7gXHQt+/Id/IWbD/3EnY8+ITVp1ohJUddRh5AmSL3LpCivmTF/9OOvvTLJ6/Ojpm3+3jdO/Pv7zsdpl4Y0hDpK/2NvetHPHEzK02VW4YEQbh9ML9v2bDyt2jRSsZ43Jv2yueTzZZwiuyvviXb6zvp0CaPzrpzxSVRduXFh1y/u3Y0Vj66c3N6648j5R1eqV744/fFrBHkFLa+f2/5nzxzTSn7gst3v2tVEmAOaXAz6N2T8Fzddu1N51PA1OYrt0Mglyy0HqeRgq/O5t7/xEKmeLBJrbv/O2X/7wDNlN1oXSCSt6w+/8cDP7+3WJypXJvfU2z9+Wk+ajosNhCTYEWtDNNZUIRjjk4pQg4OpYCoOCaTpbVBbAlbVVDJALGRgY+FqW4KGJqYokwL9KKUrfUsDGNETwgi5oio4qx1G9c5tzalOglgbwARrg7CpQzX9kqnBX/zkgataeTE8inb2/u8s/8r9D8emaYaZ1oXLS9Xbf2Tfv57bg2eRFPT4cPhfF88+kbmQeEXpxJhgdJJHVQ01+zpqDApRgY5v4KyqtkhMYVuSpFkbScppmpoprgUIJm3BLjTCNkPbWm4qDUAZ29yCnUfLoM3W22ioFj+yVQGJkYywOmpVRWN/a/CXb3jl3tDoLdMUbfvwg0d/657FNG1XqZZV5unEh19z9Xu279blHnG4z8X3D9NzzdmQwGiVwBMspEHkNyRkoaoqKiSrKU6imLGy/bveuY8fa06VwyoJjw3PPQtGq/2lM6cvmzJS54nQt/tnT4oia33lzNkDXdTIo5L1FDNoEkzLG8QQnYK19vnl0/nH33ho9xRksTeVzn7w8PIvPXgSUx1kyyhSxpn/9Krdv7Dd5oNh04a/r3H7+fZQFlKLNJYJaqPijQlMBpN0Ka1msAkUo7DRcUKFQCQlMn60KN7z5W9ZRWktXKB0p42NBxeX3nXiLp+CSlLjOFmw1LlruX/31+81kppobId2gke+OxOS0/AXQIpQHUzwhRsPXNGeR0/R1Y888o1/d2+fk20UezR0XeBDL3zRO3ftwMg31T0X8B8rdzbZ1+VllWjEEGXBgsikAMNuyn9phLHWOgcixAiiVsm11co0QmsmqAMrTNEqKanLQepiZzdgSVizslEE68teK9VsZzBp0NS+b/HxllR8vHGijrApaZnk5aGrL/1cP4YHjmexOlq3/uAR+HaW0nIUE3Xh6jk9myz/+pM5qLY+vWPFHm8wJyfP1NPQIUcCrBhmlSSKZ1Jal9lXKBExg6gsclij4NI0xHqIR8hJlKgSY7yxsS3e1shpapCK2kFSFF0YD0SiYemoF7lB2LMXxIhpQgtqvU+XTXBu0Cy1AglIYBNkTTK1Ic9VIyQmG/RzVyI0gAjnQU1IAq6TGGsya5kzjA/LFx9BVu3AEZpAFbYGgyTNQlKk5bVp+h/2H2jLQFPzyGDwK48fKzH9im7yy3v35CbnRvrw+f7vPPZ0jey1rYWfe8FOI0WRBmv5MhYTs14UCDFiEtnEroN6EAFMHIGegmyZuQhvfZ0mSNKkskLBsCpV3rESavKsNHnFWU3KriUfiXkshamcFRZSDxZV0qiuRFh54e7L3rhvjvIcKe2PMx889szx5fyfXD31hkvTGMUgHprf85FjT9dFfsO25pt3pBgxnNqAFYhFuYRWBNpp3jQqZRJELGoPJR3n+khL2y9JYLPgUnhKYxw1ZqQXhUoYgRKMEcHFpOKkxFX9x3FiU5LSU4RLYVMIOS8kTcTQyZ0WIYQaRmKVInahqdYe+ZBKgikpQ6uaWq7EeUbdjzSiYOzPH5xpliaY+a+eOfvISj+6mcgB9fnXNdoHrrrSBmEVkAoQElsj/+rTRx8jA2pWqW/5pTftmpufSwLZbmlrG2Td89Oq22xIwStRyJyR8htHjz6Yl7E5HU0esgsoerAdbhkepRCYJhg9BGg2i9mEa4NKkkTrpIcYNJtHs2HybWBnP3bFlSgdWq1H9ss7Pn/Ho4kA4cWzU5+68ZoZl1IENIIValDNohX/bmbHP//6fRcaXOvw1ivmPnbwoLE9bzgZJMiw/mUaz18Y1TSS+luXXPKjX7rnqYBAZPImTOurp+v3fuuYJlUq2TOD3mLtbCP7/LN1IsOKorU4de7oaNCAST/39KBItFYT02DrOGKw7Z15wcJlV041Hs1r5PUPzO2cbXSxsoKogIA1EhTLNq/3L+yaTu0FL4AenL/U5gYh54wxCrVQsrY9YS0rzpvf/eISfHnJnn1z082nVjQhYxFy6w5HOfzod5Ax6ibYuLbaUB6u3eGHnoYtwMaUQVotmPjtUr797SdRZmjldkRd3xkZN/ul00sPLvapMaUpf/7sqVsWd10xv91UI9ao4GgUPPCY/9MnTp+ph2inKBp/+8ThH/z+Xfvj7shFOU0c2W95hSflSfp9/H4CURY2O+/4++Wj58+hPRWjYzVEpaOEkp2V5UTr2sQsVw5VmEKccmnRVTGhO7Ck8BGuGXkqNUmNKXrJgVdFDjNVfWRQnU1bcAZas1R7gIVu24SKNApRYIiNyJOjvWrQTJ3QVKVLaXmFzS5xVCRVMGy9QdzywMHr394AaMwq9tkzS8X5zIG5UyhRMmx4W1TwsW5FwIMalrtZSCSUEka1gYnsXUzdlENa1FU0Z4mjIiFc+gLrOdgOWYEJGgOJWk48UgSPGIAIBgzAjGhhLGywsWzUsbCz5GsvBZoADIJBjJsFoLVX8clhCJwgWLKJGs9atStfkw1UvTxt/9x1L0lp1CV7uO9/67Eji9R4fWv6PVfuFR62jbun3/+tI0/mcG/pzr/7qulAOkwTizQja8BWteIQjdqIppeUURFnSgpSZSVIEgTKQTkgDZwMnBiFOsdsFOBgbKBgZROYRhWkMAqoBlJmTkMN8TFqRSRkh46RqBR6dXfm7fOz6HtI44V77AefrOHDS+d2vGE2hRbQ+rKphQ8+dTj35fWze143l2FgYI1Fv+VZwStQFZCAQAEQoR7UTEALFBWhhACMYFFbqAFTtAUYECBQFETFGFqwUQKoQAQghUFU5Ml4i0ygCYkRWMQKSMAm6tCYfrS0ZDQ4RV4nvgddGeqQba0lN0WhWscati6d1EbtJ/bPjzLvZLuClQgQRgAF8AxgoZYUhECQQEZIWcQKvKHKULM2QkRKViGktZUk6tqL3PgjGFpTv6bmxGjwVSyMrDl18kSx3GdCbaBEUA6RTNYCOoZmGlWweQIOKxmhM90edGF8JSYXA2PK0AB2pFnJtiC97WeBFjRO5ryISFo70qxjaeLQMmmRjTgIWgffoFWPV6AysR1M7RGbn01Gn6jbNmsNesv33n13FAETLMOHncbdtGtHJ4ZM6ZlK7jl1euB4eyt52Y75qboikz02GN27fF6NuZTMzQvbUo2lgYWU8LzO9OsfaBVEYNoACtLne/6/KMMWAZggViSYWEGMiT5qIAmtdpusQR2hZCIx2zN1+VdPHiEV0qicctoE2cXS/9WRx5NYAC5kLU0yEJ0I1WeffkwZkdneFaldRrI81p6KbGKIVm2yxtsq4uN5xFDeANtQaGBkgXslGl5t9E97qbRWds88fTSUFYwBgBCEWY2F6yiP8WYxClEkQqLNbk1NACAHsVCISaTbgbEAUXPvC1yZRFOvgj3WePlHvMLT2ku1coyQVKxa1Aw7ajR9YCg0hAn3q8AR6NjcRErRVCxko1VwMKpGoEJCVgyUoolilKMByBYyW3ImKMd3DfCaC/3/CLAuBE28zmkIlKnAUUWALzOlESBr3ANQYgCkxKo8icNGAWFRKI3xT0oEUsgYCUIBRgSAZZMz1RePYGPN6fND8/4fBKDJ6g/slQlCgUolMSHG8fl8XWFhhSppNBrHefdolSBGgIioNhKUo1E1CigiGyGfRAVsjJyU0SdrIJy1LX8rnG2rSM8H5tnYqrDQCIXCghCfD0Fz8QC4eneYXB7GAUMRVKERYZVAJQKQAMP2zQvbhGCwGagUN/EPFQmrTK7GpY2XdQJtHWerzGsgHZqkKTbiwAAAzERE61F1kVd9ZIIpAimskn3LdDt3pht4PcMKxE2cKKJsOeesk0ABAiXg74amHLMicezZzBPor2yRgFYBw2vA4bB6KheRMeIm1bQRxDqO20sSJ5hYbBIxzSrecyvkaP1EGyXRyPK8DeuLvehl6006Jv4H50rDBtQNAIIQeds1tultwbqxDXZ1P1hXT7ROyA1yXFyya4bWdU2byTfsjJuJ6fmkhr2okgl1JAWr3QkqWpRdRBxNEFPRjsMqrePgewO7eYN/r7G7Nq+ucr9Bqu9irrUiNOmtqwcasZp4tddNz1V63oROtMQKUhUSKNjw6uhr2lqTck0XvElhdBFWvUYUVzFiWxn+h4tuOmXBk5rcxMRcaEkz1J2yUlPr1E57T2pvGs5E5DZSNOQqAOi1qe0jb3bJLbctHYNd1ri9uIAlTrry6n7yXfx7/S16tayB/ib1hrjX9Z2q3LUsoWEEBtmOT54//X8AgWl7HAkW5nwAAAAASUVORK5CYII=
// @match         *://www.bilibili.com/*
// @exclude       *://www.bilibili.com/all*
// @exclude       *://www.bilibili.com/anime*
// @exclude       *://www.bilibili.com/pgc*
// @exclude       *://www.bilibili.com/live*
// @exclude       *://www.bilibili.com/article*
// @exclude       *://www.bilibili.com/upuser*
// @exclude       *://www.bilibili.com/match*
// @exclude       *://www.bilibili.com/platform*
// @exclude       *://www.bilibili.com/bangumi*
// @exclude       *://www.bilibili.com/cheese*
// @grant         GM_setValue
// @grant         GM_getValue
// @grant         GM_registerMenuCommand
// @compatible    chrome
// @compatible    edge
// @compatible    firefox
// ==/UserScript==

(function () {
  ("use strict");

  // >>>>> 请在浏览器右上角的油猴插件的设置面板中设置该插件的部分功能 <<<<<

  const info = {
    errKeyArr: ["", 2, 3],
    errKeyInfo: {
      disNum: "setting_err_disNum",
      errNum: "setting_err_num",
      errTime: "setting_err_time",
      isTip: "setting_err_isTip",
    },
    disErrTipNum: 3, // 每小段报错弹窗提醒次数 (短时间内的提醒次数)
    errTipNum: 5 * 3, // 报错弹窗的总提醒次数, n * disErrTipNum
    errTipInterval: 2, // 每段报错弹窗提醒时间间隔(小时)
    errNumReset: 5, // 报错次数重置的天数
  };
  const utils = {
    getPage() {
      const url = window.location.href;
      if (url.includes("/video")) {
        return "video";
      }
      return "home";
    },
    // 获取视口宽度
    getW() {
      let width =
        window.innerWidth ||
        document.documentElement.clientWidth ||
        document.body.clientWidth;
      // console.log("显示器缩放:", zoom);
      console.log("浏览器实际宽度:", width * window.devicePixelRatio);
      console.log("浏览器像素宽度:", width * zoom);
      !isAutoLayout && (width *= window.devicePixelRatio);
      return width;
    },
    // 获取存储的值, 并解析成对应数据类型
    getValue(key, defa = null) {
      let value = GM_getValue(key);
      return value === undefined || value === null ? defa : value;
    },
    // 将布尔值转为设置中显示的文本
    boolTxt(val) {
      return val ? "是 (确定)" : "否 (取消)";
    },
    // 将string转为dom对象
    strToDom(str) {
      let tmpDom = document.createElement("div");
      tmpDom.innerHTML = str;
      const dom = tmpDom.children[0];
      tmpDom = null;
      return dom;
    },
    // 日期格式化
    formatDate(
      timestamp,
      { delimiter = "-", isYear = false, isAlign = true } = {}
    ) {
      if (!timestamp) {
        return -1;
      }
      const date = new Date(timestamp);
      const year = date.getFullYear();
      let month = (date.getMonth() + 1).toString();
      let day = date.getDate().toString();
      if (isAlign) {
        month = month.padStart(2, "0");
        day = day.padStart(2, "0");
      }
      return (isYear ? year + delimiter : "") + month + delimiter + day;
    },
    // 时间格式化
    formatTime(time, { delimiter = ":", isAlign = true } = {}) {
      if (!time) {
        return -1;
      }
      let h = Math.floor(time / 60 / 60);
      let m = Math.floor((time / 60) % 60);
      let s = Math.ceil(time % 60);
      if (isAlign) {
        h = h ? h.toString().padStart(2, "0") : "";
        m = m.toString().padStart(2, "0");
        s = s.toString().padStart(2, "0");
      }
      return (h ? h + delimiter : "") + m + delimiter + s;
    },
    // 网络请求
    async request(url, options = {}) {
      options.method = options.method || "GET"; // 默认使用get方法
      try {
        const res = await fetch(url, options);
        if (!res.ok) {
          throw new Error(`HTTP error! Status: ${res.status}`);
        }
        return await res.json();
      } catch (error) {
        throw error;
      }
    },
    // 错误处理
    errHandle({ e = null, errTxt = "", logTxt = "", key = "" } = {}) {
      key && (key = "_" + key);
      let errNum = GM_getValue(info.errKeyInfo.errNum) || 0;
      if (errNum >= info.errTipNum) {
        return;
      }
      let disErrNum = GM_getValue(info.errKeyInfo.disNum + key) || 0;
      const curTime = Date.now();
      const errTime = GM_getValue(info.errKeyInfo.errTime + key) || curTime;
      let disS = (curTime - errTime) / 1000;
      disS = disS === 0 ? 5 : disS;
      if (disS < 5) {
        return;
      }
      let flag = GM_getValue(info.errKeyInfo.isTip + key); // 是否能弹窗提示
      flag = flag === undefined ? true : flag;
      e && console.log(e);
      console.log(logTxt || errTxt);
      if (disS >= info.errTipInterval * 60 * 60) {
        // 每errTipInterval小时允许提醒disErrTipNum次
        flag = true;
        GM_setValue(info.errKeyInfo.isTip + key, true);
        GM_setValue(info.errKeyInfo.disNum + key, 0);
      }
      if (
        flag &&
        disErrNum <= info.disErrTipNum &&
        disS < (info.errTipInterval / 10) * 60 * 60
      ) {
        // 在errTipInterval/10小时内允许disErrTipNum次提示
        errNum++;
        disErrNum++;
        GM_setValue(info.errKeyInfo.errNum, errNum);
        GM_setValue(info.errKeyInfo.disNum + key, disErrNum);
        GM_setValue(info.errKeyInfo.errTime + key, curTime);
        alert(errTxt);
        disErrNum === info.disErrTipNum &&
          GM_setValue(info.errKeyInfo.isTip + key, false);
      }
    },
    // 重置err相关的数据
    resetErrInfo() {
      const curTime = Date.now();
      const errTime = info.errKeyArr.reduce((a, b) => {
        const t = +GM_getValue(info.errKeyInfo.errTime + b);
        return t < a ? t : a;
      }, curTime);
      if ((curTime - errTime) / 1000 >= info.errNumReset * 24 * 60 * 60) {
        GM_setValue(info.errKeyInfo.errNum, 0); // 重置
        info.errKeyArr.forEach((key) => {
          key && (key = "_" + key);
          GM_setValue(info.errKeyInfo.disNum + key, 0); // 重置
        });
        // console.log("重置err相关的数据");
      }
    },
  };

  const page = {
    video: {
      info: {
        isDelAd: true, // 是否删除广告
        delSetting: "部分广告", // 删除的广告的设置, 横幅广告 视频广告 活动广告 活动推广 横幅通告 直播推广
        base_isDelAd: true,
        base_delSetting: "部分广告",
        startTime: 200, // 初始时间间隔
        pathInterval: 500, // 监听地址栏变化的间隔时间
        interval: 500, // 检索广告的间隔时间
        timeout: 5, // 检索广告的超时次数
      },
      doms: { ad: [] },
      delDom: {
        ad: [
          { name: "几乎全部广告", pre: ".", str: "ad-report", type: "ad" }, // 几乎全部广告 (横幅广告 视频广告 右下角广告)
          // { pre: ".", str: "ad-floor-exp" }, // 部分广告
          { name: "横幅广告", pre: "#", str: "bannerAd" }, // 评论区上方的横幅广告
          { name: "视频广告", pre: ".", str: "video-card-ad-small" }, // 右侧视频列表上方的小视频广告
          { name: "活动广告", pre: "#", str: "slide_ad", type: "ad" }, // 右侧视频列表上方的广告

          // 其他"广告"
          { name: "活动推广", pre: "#", str: "activity_vote", type: "other" }, // 评论区上方的活动推广
          { name: "横幅通告", pre: ".", str: "reply-notice", type: "other" }, // 评论区上方的横幅通告
          {
            name: "直播推广",
            pre: ".",
            str: "pop-live-small-mode",
            type: "other",
          }, // 右侧视频列表下方的直播推广
        ],
      },
      settingTxt: {
        isDelAd: `是否删除广告
默认: base_value
当前: `,
        delSetting: `广告屏蔽设置, 可根据需要自行修改, 每项用 , 分隔, 例如: "部分广告, 活动推广"
----默认: base_value
----可选:
全部广告: 所有直接广告和间接广告
部分广告: 所有直接广告 (横幅广告,视频广告,活动广告)
横幅广告: 评论区上方的横幅广告
视频广告: 右侧视频列表上方的小视频广告
活动广告: 右侧视频列表上方的广告
活动推广: 评论区上方的活动推广
横幅通告: 评论区上方的横幅通告
直播推广: 右侧视频列表下方的直播推广`,
      },
      initValue() {
        this.info.isDelAd = utils.getValue(
          "setting_video_isDelAd",
          this.info.base_isDelAd
        );
        this.info.delSetting = utils.getValue(
          "setting_video_delSetting",
          this.info.base_delSetting
        );
      },
      // 获取需要删除的广告的信息数组
      getAdInfo() {
        if (this.info.delSetting.includes("全部广告" || !this.info.isDelAd)) {
          return this.delDom.ad.filter(
            (item) => item.type === "ad" || item.type === "other"
          );
        } else if (this.info.delSetting.includes("部分广告")) {
          return this.delDom.ad.filter(
            (item) => item.type === "ad" || item.type !== "other"
          );
        } else {
          const arr = this.delDom.ad.filter((item) =>
            this.info.delSetting.includes(item.name)
          );
          if (arr.length === 0) {
            utils.errHandle({
              errTxt: `插件设置的广告屏蔽设置中 '${GM_getValue(
                "setting_video_delSetting"
              )}' 格式书写错误`,
              key: info.errKeyArr[2], // 3
            });
            return -1;
          } else {
            return arr;
          }
        }
      },
      // 获取广告dom
      getAd() {
        const ad = [];
        const delAdInfo = this.getAdInfo();
        if (delAdInfo === -1) {
          this.doms.ad = [];
          return;
        }
        delAdInfo.forEach((item) => {
          let adList;
          if (item.pre === ".") {
            adList = document.querySelectorAll(item.pre + item.str);
          } else {
            adList = [];
            ele = document.querySelector(item.pre + item.str);
            ele && adList.push(ele);
          }
          if (adList.length > 0) {
            adList.forEach((adDom) => {
              ad.push(adDom);
            });
          }
        });
        this.doms.ad = this.info.isDelAd
          ? ad.filter((item) => item.style.display !== "none")
          : ad;
        // this.doms.ad.forEach((item) => {
        //   console.log(item);
        // });
      },
      // 删除广告dom (隐藏)
      delAd() {
        this.doms.ad.forEach((item) => {
          item.style.display !== "none" && (item.style.display = "none");
        });
      },
      // 显示广告dom (复原)
      showAd() {
        this.doms.ad.forEach((item) => {
          item.style.display === "none" && (item.style.display = "");
        });
      },
      // 设置
      showSetting() {
        const txt =
          this.settingTxt.isDelAd.replace(
            "base_value",
            utils.boolTxt(this.info.base_isDelAd)
          ) + utils.boolTxt(this.info.isDelAd);
        this.info.isDelAd = confirm(txt);
        GM_setValue("setting_video_isDelAd", this.info.isDelAd);
        if (this.info.isDelAd) {
          const txt2 = this.settingTxt.delSetting.replace(
            "base_value",
            this.info.base_delSetting
          );
          this.info.delSetting = prompt(txt2, this.info.delSetting);
          !this.info.delSetting &&
            (this.info.delSetting = this.info.base_delSetting);
          GM_setValue("setting_video_delSetting", this.info.delSetting);
        }
        this.getAd();
        if (this.info.isDelAd) {
          this.delAd();
        } else {
          this.showAd();
        }
        // history.go(0); // 刷新页面
      },
      // 插件设置面板
      registerMenu() {
        GM_registerMenuCommand("去广告设置", () => {
          this.showSetting();
        });
      },
      init() {
        this.initValue();
        if (!this.info.isDelAd) {
          return;
        }
        let i = 0;
        const timer = setInterval(() => {
          i++;
          this.getAd();
          if (this.doms.ad.length === 0 || i > this.info.timeout) {
            clearInterval(timer);
          }
          this.delAd();
        }, this.info.interval);
        this.registerMenu();
        utils.resetErrInfo(); // 重置err相关的数据
      },
    },
  };

  let pageName = utils.getPage();

  // 视频播放页
  if (pageName === "video") {
    setTimeout(() => {
      page.video.init();
    }, page.video.info.startTime);
    let url = window.location.href;
    const pathInterval = page.video.info.pathInterval;
    // 地址栏变化则重新去广告
    setInterval(() => {
      if (url !== window.location.href) {
        url = window.location.href;
        setTimeout(() => {
          page.video.init();
        }, page.video.info.startTime);
      }
    }, pathInterval);
  }

  //--------------------------------------------------------------------------------------

  // 首页相关

  if (pageName !== "home") {
    return;
  }
  // 类名表 (或选择器)
  const classList = {
    标题: "h3 a",
    作者: "bili-video-card__info--owner", // 含日期. 仅作者: bili-video-card__info--author
    分类: "floor-title",
    carousel: "recommended-swipe", // 轮播图
    vDom: ["container", "no-banner-container", "is-version8"], // 视频区域 的容器元素
    video: "feed-card", // 换一换的视频 (一般是前十个)
    nav: "bili-header__bar", // 导航栏的元素
    banner: "bili-header__banner", // 横幅背景的元素
    btn: "roll-btn", // 右侧换一换按钮
    btn2: "flexible-roll-btn", // 新版右下角换一换按钮
  };
  let vDom, nav, banner, carousel;

  // 默认值
  const base_isClearAd = true; // 是否删除'广告'(屏蔽视频). 默认 true
  const base_isTrueEnd = false; // 是否将广告移至预加载视频的后面. 默认 false
  const base_isAutoLayout = true; // 是否根据缩放自动布局 (是否根据像素宽度布局). 默认 true
  const base_isLoadOne = true; // 是否视频全加载. 默认 true
  const base_isCarousel = true; // 是否显示轮播图. 默认 true
  const base_videoNumRule =
    "0,1300,2; 1300,1800,3; 1800,3000,4; 3000,3700,5; 3700,6300,6"; // 视频排列规则, 其他尺寸按照初始方式排列
  const base_videoNumRule2 =
    "0,1300,4; 1300,1800,5; 1800,3000,6; 3000,3700,7; 3700,6300,8"; // 无轮播图
  const base_delClassArr = "广告, 推广"; // 屏蔽的类名列表, 子元素包含某类名也可屏蔽

  let isClearAd,
    isTrueEnd,
    isAutoLayout,
    isLoadOne,
    isCarousel,
    videoNumRule,
    delClassArr;

  // console.log(isClearAd, isTrueEnd, isLoadOne, videoNumRule, delClassArr);

  // 屏蔽的类名表
  const delClassList = {
    广告: "bili-video-card__info--ad",
    推广: "bili-video-card__info--creative-ad",
    特殊: "floor-single-card",
    直播: "living", // 分类=直播
    番剧: "分类=番剧",
    综艺: "分类=综艺",
    课堂: "分类=课堂",
    漫画: "分类=漫画",
    国创: "分类=国创",
    电影: "分类=电影",
    纪录片: "分类=纪录片",
    电视剧: "分类=电视剧",
  };
  // 设置的文本
  const settingText = {
    isClearAd: `是否删除广告, 若不删除则会将所有广告移至视频列表的最后
默认: ${utils.boolTxt(base_isClearAd)}
当前: `,
    isTrueEnd: `是否将广告移至预加载视频的后面, 关闭后广告将放置在预加载视频的前面 一般视频的后面
默认: ${utils.boolTxt(base_isTrueEnd)}
当前: `,
    isAutoLayout: `是否根据缩放自动调整视频布局
默认: ${utils.boolTxt(base_isAutoLayout)}
当前: `,
    isLoadOne: `是否在进入网站时加载视口区域的全部视频, 开启时视频将会全部加载(不会出现空白视频), 可能会闪一下
默认: ${utils.boolTxt(base_isLoadOne)}
当前: `,
    isCarousel: `是否显示轮播图 (注: 修改此项后需要调整排列规则以适应新的布局,每行显示视频数+-2即可)
默认: ${utils.boolTxt(base_isCarousel)}
当前: `,
    delClassArr: `屏蔽设置, 可根据需要自行修改, 可自定义, 每项用 ; 分隔
----默认:
${base_delClassArr}
----可选: ('特殊'包含了直播 番剧 课堂...)
广告、推广、特殊、直播、番剧、综艺、课堂、漫画、国创、电影、纪录片、电视剧
----自定义:
1. 标题=xxx, 可屏蔽标题含xxx的视频, xxx部分支持&&运算符, 如: 标题=A&&B, 表示屏蔽标题同时含有A B内容的视频
2. 作者=xxx, 可屏蔽作者名和发布日期中含xxx的视频`,
    videoNumRule: `视频排列规则, 每条规则用 ; 分隔. 其他尺寸按照初始方式排列
示例: 1450,2400,4 表示浏览器宽度在1450~2400像素时每行显示4个视频(前两行)
默认:
${base_videoNumRule} (有轮播图)
${base_videoNumRule2} (无轮播图)`,
  };
  const apiUrl =
    "https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd"; // 请求视频的api
  const imgDetails = "@672w_378h_1c_!web-home-common-cover"; // 视频dom中封面图的url的附加信息
  const queryNum = 0; // 处理的视频数量, 对前 queryNum 个视频中的广告进行处理(删除或置后), 0表示对全部视频进行处理. 默认 0
  const marginTop1 = 40; // 第三行视频的上边距
  const marginTop2 = 24; // 第四行及以上视频的上边距
  const zoom = window.devicePixelRatio; // 获取浏览器缩放 (包括显示器缩放的影响)

  let cssDom;
  let cssText;
  let oldCssText;
  let isChange = false; // 每行视频数是否需要变化
  let rowVideoNum = 3; // 当前每行显示的视频数 (以第一行为准), 网站默认值为3
  let videoNum = 0; // 视频总数
  let newVideoNum = 0; // 新获取的视频总数
  let firstAdIndex = 0; // 第一个广告的索引
  let pageZoom = 1; // 页面缩放

  initValue();
  getDoms();
  if (!vDom) {
    return;
  }
  let w = utils.getW(); // 浏览器视口宽度
  videoNum = getVideoNum(vDom); // 计算当前视频总数
  let adArr = getAd(queryNum, delClassArr, newVideoNum, 1);
  delAd(adArr, vDom); // 将所有广告放置在最后 或 删除
  setTimeout(() => {
    delAdFn();
  }, 1000);
  zoomPage(); // 缩放页面
  setStyle(); // 调整视频排列
  loadTopVideo(); // 加载空白视频
  registerMenu(); // 插件菜单
  utils.resetErrInfo(); // 重置err相关的数据

  let rollBtn, btnSvg, rollBtn2;
  // 刷新视频
  function handleClick() {
    if (!rollBtn) {
      adArr = getAd(
        rowVideoNum * 3 + (carousel ? 2 : 0),
        delClassArr,
        newVideoNum,
        1
      );
      delAd(adArr, vDom);
      rollBtn = document.querySelector("button." + classList.btn); // 换一换按钮
      btnSvg = rollBtn && rollBtn.querySelector("svg"); // 换一换按钮的旋转图标
      // 点击按钮后对新视频中的广告进行处理
      if (btnSvg) {
        btnSvg.addEventListener("transitionend", () => {
          // console.log("视频刷新成功");
          adArr = getAd(
            rowVideoNum * 3 + (carousel ? 2 : 0) + 3,
            delClassArr,
            newVideoNum,
            1
          );
          !isTrueEnd &&
            adArr.forEach((item) => {
              item.forEach((adItem) => {
                adItem.style.display = "block";
              });
            });
          setTimeout(() => {
            delAd(adArr, vDom);
          }, 50);
        });
      } else {
        rollBtn &&
          rollBtn.addEventListener("click", () => {
            setTimeout(() => {
              adArr = getAd(
                rowVideoNum * 3 + (carousel ? 2 : 0) + 3,
                delClassArr,
                newVideoNum,
                1
              );
              delAd(adArr, vDom);
            }, 800);
          });
      }
    }
    if (!rollBtn2) {
      adArr = getAd(queryNum, delClassArr, newVideoNum, 1);
      delAd(adArr, vDom);
      rollBtn2 = document.querySelector("." + classList.btn2); // 新版右下角的换一换按钮
      // 点击按钮后对新视频中的广告进行处理
      rollBtn2 &&
        rollBtn2.addEventListener("click", () => {
          setTimeout(() => {
            videoNum = getVideoNum(vDom); // 计算当前视频总数
            firstAdIndex = 0;
            adArr = getAd(
              queryNum,
              delClassArr,
              0,
              1,
              isTrueEnd ? true : false
            );
            delAd(adArr, vDom, true); // 强制删除广告
            loadTopVideo();
          }, 800);
        });
    }
    if (rollBtn && rollBtn2) {
      window.removeEventListener("click", handleClick);
    }
  }
  window.addEventListener("click", handleClick);

  // 窗口调整后重新计算视频的行数量
  let timer;
  window.addEventListener("resize", () => {
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      // console.log("窗口改变");
      const newW = utils.getW();
      if (newW > w) {
        delAdFn(); // 若新增广告则删除
      }
      w = newW;
      zoomPage();
      setStyle();
    }, 400);
  });

  // 加载的新视频去除广告
  let timer2, timer3;
  window.addEventListener("wheel", () => {
    timer2 && clearTimeout(timer2);
    timer3 && clearTimeout(timer3);
    timer2 = setTimeout(() => {
      delAdFn(timer3);
    }, 600);
    timer3 = setTimeout(() => {
      delAdFn();
    }, 1500);
  });

  function initValue() {
    isClearAd = getValue("setting_isClearAd", base_isClearAd);
    isTrueEnd = getValue("setting_isTrueEnd", base_isTrueEnd);
    isAutoLayout = getValue("setting_isAutoLayout", base_isAutoLayout);
    isLoadOne = getValue("setting_isLoadOne", base_isLoadOne);
    isCarousel = getValue("setting_isCarousel", base_isCarousel);
    delClassArr = getValue("setting_delClassArr", base_delClassArr);
  }

  function getDoms() {
    if (typeof classList.vDom === "string") {
      vDom = document.querySelector("." + classList.vDom);
    } else {
      classList.vDom.forEach((item) => {
        !vDom && (vDom = document.querySelector("." + item));
      });
    }
    if (!vDom) {
      vDom = document.querySelector("." + classList.video).parentElement;
    }
    carousel = vDom.querySelector("." + classList.carousel);
    nav = document.querySelector("." + classList.nav);
    banner = document.querySelector("." + classList.banner);

    videoNumRule = getValue(
      "setting_videoNumRule",
      carousel && isCarousel ? base_videoNumRule2 : base_videoNumRule
    );
    if (!isCarousel) {
      carousel.style.display = "none";
      carousel = false;
    }
  }

  // 缩放页面 至消除横向滚动条
  function zoomPage() {
    if (document.getBoxObjectFor) {
      return; // 火狐不支持zoom, 而用scale会有bug
    }
    const rootDom = document.documentElement;
    let rate = rootDom.scrollWidth / getMainW();
    !document.body.style.overflow &&
      (document.body.style.overflow = "hidden auto");
    if (rate > 1) {
      // 存在横向滚动条
      pageZoom *= 1 / rate;
      rootDom.style.zoom = pageZoom;
    } else {
      pageZoom = 1;
      rootDom.style.zoom = 1;
      rate = rootDom.scrollWidth / getMainW();
      if (rate > 1) {
        pageZoom *= 1 / rate;
        rootDom.style.zoom = pageZoom;
      }
    }
    // 主区域的宽度, 部分时候总宽(导航栏)会大于主区域的宽度
    function getMainW() {
      let navW = nav ? nav.scrollWidth : 0;
      let bannerW = banner ? banner.scrollWidth : 0;
      return navW > bannerW ? navW : rootDom.clientWidth;
    }
  }

  /**
   * 获取所有的 推广 和 广告 的元素的列表
   * @param {*} queryNum 需要检索的视频数量
   * @param {Array} delClassArr 需要删除的类名列表
   * @param {*} vNum 视频总数
   * @param {*} startIndex 检索的视频的起始索引位
   * @param {Boolean} isAll 是否检索预加载视频以及后面的视频
   * @returns {Array} 含各类广告列表的列表 [[...],[...],...]
   */
  function getAd(queryNum, delClassArr, vNum, startIndex = 1, isAll = false) {
    const arr = [];
    delClassArr.forEach(() => {
      arr.push([]);
    });
    const vList = [].slice.call(vDom.children);
    let len = vNum || vList.length;
    len = len > vList.length ? vList.length : len;
    queryNum = queryNum || len; // 0则全检索
    queryNum += startIndex;
    if (queryNum > len) {
      queryNum = len;
    }
    // console.log("queryNum, vNum, startIndex, len\n",queryNum,vNum,startIndex,len);
    for (let i = startIndex; i < queryNum; i++) {
      const vItem = vList[i];
      // console.log(i, item);
      if (!isAll && !vItem.querySelector("a")) {
        break; // 如果是预加载的视频
      }
      for (let j = 0; j < delClassArr.length; j++) {
        if (isChecked(vItem, delClassArr[j])) {
          arr[j].push(vItem);
          break;
        }
      }
    }
    // console.log("广告列表:", arr);
    return arr;
  }

  // 删除广告 或 放置在最后
  function delAd(adArr, dom = vDom, isDel = false) {
    for (let i = adArr.length - 1; i >= 0; i--) {
      delInArr(adArr[i]);
    }
    function delInArr(arr) {
      arr.forEach((item) => {
        if (isClearAd || isDel) {
          item.remove();
          newVideoNum--;
          videoNum--;
        } else {
          if (isTrueEnd) {
            newVideoNum--;
            videoNum--;
            dom.appendChild(item); // 放在最后 (预加载视频后)
          } else {
            dom.insertBefore(item, dom.children[newVideoNum]); // 放在预加载视频前
          }
        }
      });
    }
  }

  // 根据视频总数是否变化删除广告
  function delAdFn(timer = null) {
    getVideoNum(vDom);
    if (newVideoNum > videoNum) {
      console.log("加载新视频");
      adArr = getAd(
        queryNum,
        delClassArr,
        newVideoNum,
        isTrueEnd ? videoNum : firstAdIndex
      );
      delAd(adArr, vDom);
      videoNum = newVideoNum;
      timer && clearTimeout(timer);
    }
  }

  // 设置浏览器宽度在某个范围时[左闭右开], 每行显示的视频数
  function setVideoNum(vRule) {
    const min = +vRule[0] / (isAutoLayout ? zoom : 1);
    const max = +vRule[1] / (isAutoLayout ? zoom : 1);
    const num = +vRule[2];
    // console.log(min, max, num, ">", w);
    if ((min !== 0 && !min) || !max || !num) {
      utils.errHandle({
        errTxt: `插件设置的视频排列规则设置中 '${vRule.join("")}' 格式书写错误`,
        key: info.errKeyArr[1], // 2
      });
      return;
    }
    if (w >= min && w < max) {
      cssText = carousel
        ? `.container {grid-template-columns: repeat(${num + 2},1fr) !important}
.container>div:nth-child(n){margin-top:${marginTop2}px !important}
.container>div:nth-child(-n+${
            num * 3 + 2 + 1
          }){margin-top:${marginTop1}px !important;display:block !important}
.container>div:nth-child(-n+${(num + 1) * 2 - 1}){margin-top:0px !important}`
        : `.container {grid-template-columns: repeat(${num},1fr) !important}
.container>div:nth-child(n){margin-top:${marginTop2}px !important}
      `;
      isChange = true;
      rowVideoNum = num;
      // console.log("每行 " + num + " 个视频");
    }
    if (!isChange) {
      cssText = ""; // 默认排列方式
      rowVideoNum = carousel ? 5 : 3;
    }
  }

  // 调整每行显示个数
  function setStyle() {
    isChange = false; // 每行视频数是否需要变化
    videoNumRule.forEach((item) => {
      setVideoNum(item);
    });
    if (isChange) {
      let isCssDom = !!cssDom; // 是否已添加style
      if (!isCssDom) {
        cssDom = document.createElement("style");
        cssDom.setAttribute("type", "text/css");
      }
      oldCssText !== cssText && (cssDom.innerHTML = cssText);
      oldCssText = cssText;
      !isCssDom && vDom.parentElement.insertBefore(cssDom, vDom);
    } else {
      // 尺寸缩小时触发
      if (!isChange && cssDom) {
        oldCssText = "";
        cssDom.innerHTML = "";
      }
    }
  }

  // 获取视频总数
  function getVideoNum(dom) {
    const arr = [].slice.call(dom.children);
    const len = arr.length;
    let i;
    let isGetAdIndex = false;
    for (i = 1; i < len; i++) {
      const item = arr[i];
      // 获取第一个广告的索引
      if (!isTrueEnd && !isGetAdIndex) {
        const vItem = dom.children[i];
        for (let j = 0; j < delClassArr.length; j++) {
          if (isChecked(vItem, delClassArr[j])) {
            isGetAdIndex = true;
            firstAdIndex = i;
            break;
          }
        }
      }
      // 如果是预加载视频
      if (!item.querySelector("a")) {
        newVideoNum = i;
        return i;
      }
    }
    newVideoNum = i;
    return i;
  }

  // 判断是否是查找的目标
  function isChecked(vEle, delStr) {
    let flag = false;
    const map = classList;
    delStr = delClassList[delStr] || delStr;
    // 自定义的屏蔽内容
    function custom(txt, type, selector) {
      const dom = vEle.querySelector(selector);
      if (!dom) {
        return;
      }
      const domTxt = dom.innerText;
      const txtArr = txt.replace(type, "").split("&&");
      if (!txtArr[0]) {
        return;
      }
      let f = false;
      txtArr.forEach((item) => {
        f = f || domTxt.includes(item, "");
      });
      flag = flag || f;
    }
    if (delStr.includes("标题=")) {
      custom(delStr, "标题=", map.标题);
    } else if (delStr.includes("作者=")) {
      custom(delStr, "作者=", "." + map.作者);
    } else if (delStr.includes("分类=")) {
      custom(delStr, "分类=", "." + map.分类);
    } else {
      flag = flag || vEle.classList.contains(delStr);
      try {
        flag = flag || vEle.querySelector("." + delStr);
      } catch (e) {
        utils.errHandle({
          errTxt: `插件设置的屏蔽设置中 '${delStr}' 格式书写错误应以 '标题=' 或 '作者=' 开头`,
          e,
        });
      }
    }
    return flag;
  }

  // 将数据转成视频的dom的列表
  function createVideoDom(data) {
    // 格式化数据 如: 159210 > 15.9万
    function formatNum(num) {
      if (num > 100000000) {
        return (num / 100000000).toFixed(1) + "亿";
      } else if (num > 10000) {
        return (num / 10000).toFixed(1) + "万";
      } else {
        return "" + num;
      }
    }
    // 格式化日期 如: 1699588800 > 11-10, 时间小于一天则转为'xx小时前'
    function formatDate(t) {
      const date = new Date();
      const oldDate = new Date(t * 1000);
      const curTime = Math.floor(date.getTime() / 1000); // s
      const dis = curTime - t;
      const year = date.getFullYear();
      const oldYear = oldDate.getFullYear();
      if (year !== oldYear) {
        return utils.formatDate(t * 1000, { isYear: true, isAlign: false }); // 不是今年
      } else {
        if (dis < 60) {
          return "刚刚"; // 1分钟以内
        } else if (dis < 60 * 60) {
          return Math.floor(dis / 60 / 60) + "分钟前"; // 1小时以内
        } else if (dis < 24 * 60 * 60) {
          return Math.floor(dis / 60 / 60) + "小时前"; // 1天以内
        } else if (dis < 2 * 24 * 60 * 60) {
          return "昨天"; // 2天以内
        } else {
          return utils.formatDate(t * 1000, { isAlign: false });
        }
      }
    }
    // 一个视频的HTML
    function videoDom(item, reportStr) {
      if (item.business_info) {
        return -1; // 如果是广告
      }
      const like = Math.floor(item.stat.like / 10000); // 点赞数, 万
      const likeHTML =
        like > 1
          ? `<div class="bili-video-card__info--icon-text">${like}万点赞</div>`
          : "";
      const upIconHTML = likeHTML
        ? ""
        : `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" width="24"
  height="24" fill="currentColor" class="bili-video-card__info--owner__up"><!--[-->
  <path
    d="M6.15 8.24805C6.5642 8.24805 6.9 8.58383 6.9 8.99805L6.9 12.7741C6.9 13.5881 7.55988 14.248 8.3739 14.248C9.18791 14.248 9.8478 13.5881 9.8478 12.7741L9.8478 8.99805C9.8478 8.58383 10.1836 8.24805 10.5978 8.24805C11.012 8.24805 11.3478 8.58383 11.3478 8.99805L11.3478 12.7741C11.3478 14.41655 10.01635 15.748 8.3739 15.748C6.73146 15.748 5.4 14.41655 5.4 12.7741L5.4 8.99805C5.4 8.58383 5.73578 8.24805 6.15 8.24805z"
    fill="currentColor"></path>
  <path
    d="M12.6522 8.99805C12.6522 8.58383 12.98795 8.24805 13.4022 8.24805L15.725 8.24805C17.31285 8.24805 18.6 9.53522 18.6 11.123C18.6 12.71085 17.31285 13.998 15.725 13.998L14.1522 13.998L14.1522 14.998C14.1522 15.4122 13.8164 15.748 13.4022 15.748C12.98795 15.748 12.6522 15.4122 12.6522 14.998L12.6522 8.99805zM14.1522 12.498L15.725 12.498C16.4844 12.498 17.1 11.8824 17.1 11.123C17.1 10.36365 16.4844 9.74804 15.725 9.74804L14.1522 9.74804L14.1522 12.498z"
    fill="currentColor"></path>
  <path
    d="M12 4.99805C9.48178 4.99805 7.283 5.12616 5.73089 5.25202C4.65221 5.33949 3.81611 6.16352 3.72 7.23254C3.60607 8.4998 3.5 10.171 3.5 11.998C3.5 13.8251 3.60607 15.4963 3.72 16.76355C3.81611 17.83255 4.65221 18.6566 5.73089 18.7441C7.283 18.8699 9.48178 18.998 12 18.998C14.5185 18.998 16.7174 18.8699 18.2696 18.74405C19.3481 18.65655 20.184 17.8328 20.2801 16.76405C20.394 15.4973 20.5 13.82645 20.5 11.998C20.5 10.16965 20.394 8.49877 20.2801 7.23205C20.184 6.1633 19.3481 5.33952 18.2696 5.25205C16.7174 5.12618 14.5185 4.99805 12 4.99805zM5.60965 3.75693C7.19232 3.62859 9.43258 3.49805 12 3.49805C14.5677 3.49805 16.8081 3.62861 18.3908 3.75696C20.1881 3.90272 21.6118 5.29278 21.7741 7.09773C21.8909 8.3969 22 10.11405 22 11.998C22 13.88205 21.8909 15.5992 21.7741 16.8984C21.6118 18.7033 20.1881 20.09335 18.3908 20.23915C16.8081 20.3675 14.5677 20.498 12 20.498C9.43258 20.498 7.19232 20.3675 5.60965 20.2392C3.81206 20.0934 2.38831 18.70295 2.22603 16.8979C2.10918 15.5982 2 13.8808 2 11.998C2 10.1153 2.10918 8.39787 2.22603 7.09823C2.38831 5.29312 3.81206 3.90269 5.60965 3.75693z"
    fill="currentColor"></path><!--]-->
</svg>`;
      const html = `<div data-v-7ae03d4e="" class="bili-video-card is-rcmd" data-report="tianma.${reportStr}.click"
  style="--cover-radio: 56.25%;">
  <div class="bili-video-card__skeleton hide">
    <div class="bili-video-card__skeleton--cover"></div>
    <div class="bili-video-card__skeleton--info">
      <div class="bili-video-card__skeleton--right">
        <p class="bili-video-card__skeleton--text"></p>
        <p class="bili-video-card__skeleton--text short"></p>
        <p class="bili-video-card__skeleton--light"></p>
      </div>
    </div>
  </div>
  <div class="bili-video-card__wrap __scale-wrap"><a href="${
    item.uri
  }" target="_blank" data-spmid="333.1007"
      data-mod="tianma.${reportStr}" data-idx="click">
      <div class="bili-video-card__image __scale-player-wrap bili-video-card__image--hover">
        <div class="bili-video-card__image--wrap">
          <div class="bili-watch-later" style="display: none;"><svg class="bili-watch-later__icon">
              <use xlink:href="#widget-watch-later"></use>
            </svg><span class="bili-watch-later__tip" style="display: none;"></span></div>
          <picture class="v-img bili-video-card__cover">
            <source
              srcset="${item.pic.replace("http:", "")}${imgDetails}.avif"
              type="image/avif">
            <source
              srcset="${item.pic.replace("http:", "")}${imgDetails}.webp"
              type="image/webp"><img
              src="${item.pic.replace("http:", "")}${imgDetails}"
              alt="${item.title}" loading="eager" onload=""
              onerror="typeof window.imgOnError === 'function' && window.imgOnError(this)">
          </picture>
          <div class="v-inline-player"></div>
        </div>
        <div class="bili-video-card__mask">
          <div class="bili-video-card__stats">
            <div class="bili-video-card__stats--left"><span class="bili-video-card__stats--item"><svg
                  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"
                  width="24" height="24" fill="#ffffff" class="bili-video-card__stats--icon">
                  <path
                    d="M12 4.99805C9.48178 4.99805 7.283 5.12616 5.73089 5.25202C4.65221 5.33949 3.81611 6.16352 3.72 7.23254C3.60607 8.4998 3.5 10.171 3.5 11.998C3.5 13.8251 3.60607 15.4963 3.72 16.76355C3.81611 17.83255 4.65221 18.6566 5.73089 18.7441C7.283 18.8699 9.48178 18.998 12 18.998C14.5185 18.998 16.7174 18.8699 18.2696 18.74405C19.3481 18.65655 20.184 17.8328 20.2801 16.76405C20.394 15.4973 20.5 13.82645 20.5 11.998C20.5 10.16965 20.394 8.49877 20.2801 7.23205C20.184 6.1633 19.3481 5.33952 18.2696 5.25205C16.7174 5.12618 14.5185 4.99805 12 4.99805zM5.60965 3.75693C7.19232 3.62859 9.43258 3.49805 12 3.49805C14.5677 3.49805 16.8081 3.62861 18.3908 3.75696C20.1881 3.90272 21.6118 5.29278 21.7741 7.09773C21.8909 8.3969 22 10.11405 22 11.998C22 13.88205 21.8909 15.5992 21.7741 16.8984C21.6118 18.7033 20.1881 20.09335 18.3908 20.23915C16.8081 20.3675 14.5677 20.498 12 20.498C9.43258 20.498 7.19232 20.3675 5.60965 20.2392C3.81206 20.0934 2.38831 18.70295 2.22603 16.8979C2.10918 15.5982 2 13.8808 2 11.998C2 10.1153 2.10918 8.39787 2.22603 7.09823C2.38831 5.29312 3.81206 3.90269 5.60965 3.75693z"
                    fill="currentColor"></path>
                  <path
                    d="M14.7138 10.96875C15.50765 11.4271 15.50765 12.573 14.71375 13.0313L11.5362 14.8659C10.74235 15.3242 9.75 14.7513 9.75001 13.8346L9.75001 10.1655C9.75001 9.24881 10.74235 8.67587 11.5362 9.13422L14.7138 10.96875z"
                    fill="currentColor"></path>
                </svg><span class="bili-video-card__stats--text">${formatNum(
                  item.stat.view
                )}</span></span><span
                class="bili-video-card__stats--item"><svg xmlns="http://www.w3.org/2000/svg"
                  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" width="24" height="24" fill="#ffffff"
                  class="bili-video-card__stats--icon">
                  <path
                    d="M12 4.99805C9.48178 4.99805 7.283 5.12616 5.73089 5.25202C4.65221 5.33949 3.81611 6.16352 3.72 7.23254C3.60607 8.4998 3.5 10.171 3.5 11.998C3.5 13.8251 3.60607 15.4963 3.72 16.76355C3.81611 17.83255 4.65221 18.6566 5.73089 18.7441C7.283 18.8699 9.48178 18.998 12 18.998C14.5185 18.998 16.7174 18.8699 18.2696 18.74405C19.3481 18.65655 20.184 17.8328 20.2801 16.76405C20.394 15.4973 20.5 13.82645 20.5 11.998C20.5 10.16965 20.394 8.49877 20.2801 7.23205C20.184 6.1633 19.3481 5.33952 18.2696 5.25205C16.7174 5.12618 14.5185 4.99805 12 4.99805zM5.60965 3.75693C7.19232 3.62859 9.43258 3.49805 12 3.49805C14.5677 3.49805 16.8081 3.62861 18.3908 3.75696C20.1881 3.90272 21.6118 5.29278 21.7741 7.09773C21.8909 8.3969 22 10.11405 22 11.998C22 13.88205 21.8909 15.5992 21.7741 16.8984C21.6118 18.7033 20.1881 20.09335 18.3908 20.23915C16.8081 20.3675 14.5677 20.498 12 20.498C9.43258 20.498 7.19232 20.3675 5.60965 20.2392C3.81206 20.0934 2.38831 18.70295 2.22603 16.8979C2.10918 15.5982 2 13.8808 2 11.998C2 10.1153 2.10918 8.39787 2.22603 7.09823C2.38831 5.29312 3.81206 3.90269 5.60965 3.75693z"
                    fill="currentColor"></path>
                  <path
                    d="M15.875 10.75L9.875 10.75C9.46079 10.75 9.125 10.4142 9.125 10C9.125 9.58579 9.46079 9.25 9.875 9.25L15.875 9.25C16.2892 9.25 16.625 9.58579 16.625 10C16.625 10.4142 16.2892 10.75 15.875 10.75z"
                    fill="currentColor"></path>
                  <path
                    d="M17.375 14.75L11.375 14.75C10.9608 14.75 10.625 14.4142 10.625 14C10.625 13.5858 10.9608 13.25 11.375 13.25L17.375 13.25C17.7892 13.25 18.125 13.5858 18.125 14C18.125 14.4142 17.7892 14.75 17.375 14.75z"
                    fill="currentColor"></path>
                  <path
                    d="M7.875 10C7.875 10.4142 7.53921 10.75 7.125 10.75L6.625 10.75C6.21079 10.75 5.875 10.4142 5.875 10C5.875 9.58579 6.21079 9.25 6.625 9.25L7.125 9.25C7.53921 9.25 7.875 9.58579 7.875 10z"
                    fill="currentColor"></path>
                  <path
                    d="M9.375 14C9.375 14.4142 9.03921 14.75 8.625 14.75L8.125 14.75C7.71079 14.75 7.375 14.4142 7.375 14C7.375 13.5858 7.71079 13.25 8.125 13.25L8.625 13.25C9.03921 13.25 9.375 13.5858 9.375 14z"
                    fill="currentColor"></path>
                </svg><span class="bili-video-card__stats--text">${formatNum(
                  item.stat.danmaku
                )}</span></span></div><span
              class="bili-video-card__stats__duration">${utils.formatTime(
                item.duration
              )}</span>
          </div>
        </div>
      </div>
    </a>
    <div class="bili-video-card__info __scale-disable"><!---->
      <div class="bili-video-card__info--right">
        <h3 class="bili-video-card__info--tit" title="${item.title}">
          <a href="${
            item.uri
          }?spm_id_from=333.1007.tianma.${reportStr}.click" target="_blank" data-spmid="333.1007"
            data-mod="tianma.${reportStr}" data-idx="click">${item.title}
          </a>
        </h3>
        <div class="bili-video-card__info--bottom">
          ${likeHTML}
          <a class="bili-video-card__info--owner" href="//space.bilibili.com/${
            item.owner.mid
          }" target="_blank"
            data-spmid="333.1007" data-mod="tianma.${reportStr}" data-idx="click">
            ${upIconHTML}
            <span class="bili-video-card__info--author" title="${
              item.owner.name
            }">${item.owner.name}</span>
            <span class="bili-video-card__info--date">· ${formatDate(
              item.pubdate
            )}</span>
          </a>
        </div>
      </div>
    </div>
  </div>
</div>`;
      return utils.strToDom(html);
    }
    const videoList = [];
    const row = 4;
    let num = 10;
    data.forEach((item, index) => {
      num++;
      const reportStr = `${row}-${index + 1}-${num}`;
      const dom = videoDom(item, reportStr);
      // console.log("当前视频dom", dom);
      dom !== -1 && videoList.push(dom);
    });
    return videoList;
  }

  // 加载顶部位置的接下来的一组视频
  async function loadTopVideo() {
    // 计算需要新增的视频数
    function addVideoNum() {
      // 有轮播图
      if (isCarousel) {
        return (
          rowVideoNum * 3 + 2 - newVideoNum + (!isClearAd && !isTrueEnd ? 1 : 0)
        );
      } else {
        return (
          rowVideoNum * 3 - newVideoNum + (!isClearAd && !isTrueEnd ? 1 : 0)
        );
      }
    }
    if (isLoadOne) {
      try {
        // throw new Error(`test`);
        const row = 4;
        const newNum = addVideoNum();
        const params = new URLSearchParams({
          web_location: 1430650,
          y_num: 4,
          fresh_type: 4,
          feed_version: "V8",
          fresh_idx_1h: 1,
          fetch_row: row,
          fresh_idx: 1,
          brush: 1,
          homepage_ver: 1,
          ps: newNum,
          last_y_num: 5,
          screen: window.innerWidth + "-" + window.innerHeight,
          wts: Math.floor(new Date().getTime() / 1000),
        });
        const data = await utils.request(`${apiUrl}?${params}`);
        // console.log("请求数据:", data);
        let videoList = [];
        if (data.data.item) {
          videoList = createVideoDom(data.data.item);
          getVideoNum(vDom);
          // console.log("请求的视频dom列表:", videoList);
          for (let i = videoList.length - 1; i >= 0; i--) {
            vDom.insertBefore(videoList[i], vDom.children[newVideoNum]); // 放在预加载视频前
          }
        }
      } catch (e) {
        console.log(e);
        setTimeout(() => {
          document.documentElement.scrollTo(0, 400);
          setTimeout(() => {
            document.documentElement.scrollTo(0, 0);
            setTimeout(() => {
              delAdFn();
            }, 800);
          }, 20);
        }, 1000);
      }
    }
  }

  // 获取存储的值, 并解析成对应数据类型
  function getValue(key, defa = "") {
    let value = GM_getValue(key);
    if (key === "setting_videoNumRule") {
      if (value !== undefined && value !== null) {
        value = getVideoNumRule(value);
      } else {
        defa = getVideoNumRule(defa);
      }
    } else if (key === "setting_delClassArr") {
      if (value !== undefined && value !== null) {
        value = getDelClassArr(value);
      } else {
        defa = getDelClassArr(defa);
      }
    }
    return value === undefined || value === null ? defa : value;
  }

  // 解析数据字符串为对应数据类型
  function getDelClassArr(value) {
    value = value.replaceAll("\n", "").replaceAll(" ", "");
    return value.split(/;|;|,|,/);
  }
  function getVideoNumRule(value) {
    value = value.split(/;|;/);
    return value.map((item) => item.split(/,|,/));
  }

  // 基础设置
  function basicSetting(txt) {
    const val = {
      v1: GM_getValue("setting_isLoadOne"),
      v2: GM_getValue("setting_isCarousel"),
      v3: GM_getValue("setting_isAutoLayout"),
      v4: GM_getValue("setting_isClearAd"),
      v5: base_isTrueEnd,
    };
    const newVal = {
      v1: confirm(
        txt.isLoadOne +
          utils.boolTxt(val.v1 === undefined ? base_isLoadOne : val.v1)
      ),
      v2: confirm(
        txt.isCarousel +
          utils.boolTxt(val.v2 === undefined ? base_isCarousel : val.v2)
      ),
      v3: confirm(
        txt.isAutoLayout +
          utils.boolTxt(val.v3 === undefined ? base_isAutoLayout : val.v3)
      ),
      v4: confirm(
        txt.isClearAd +
          utils.boolTxt(val.v4 === undefined ? base_isClearAd : val.v4)
      ),
      v5: base_isTrueEnd,
    };
    GM_setValue("setting_isLoadOne", newVal.v1);
    GM_setValue("setting_isCarousel", newVal.v2);
    GM_setValue("setting_isAutoLayout", newVal.v3);
    GM_setValue("setting_isClearAd", newVal.v4);
    if (!newVal.v4) {
      val.v5 = GM_getValue("setting_isTrueEnd");
      newVal.v5 = confirm(
        txt.isTrueEnd +
          utils.boolTxt(val.v5 === undefined ? base_isTrueEnd : val.v5)
      );
      GM_setValue("setting_isTrueEnd", newVal.v5);
    } else {
      GM_setValue("setting_isTrueEnd", base_isTrueEnd);
    }
    let isRefresh = false;
    for (let key in newVal) {
      if (val[key] !== newVal[key]) {
        isRefresh = true;
        break;
      }
    }
    if (isRefresh) {
      history.go(0); // 刷新页面
    }
  }

  // 屏蔽设置
  function delSetting(txt) {
    const value = GM_getValue("setting_delClassArr");
    const newValue = prompt(
      txt.delClassArr,
      value === undefined || value === null ? base_delClassArr : value
    );
    GM_setValue("setting_delClassArr", newValue || value);
    delClassArr = getDelClassArr(newValue || value);
    let adArr = getAd(queryNum, delClassArr, newVideoNum, 1);
    delAd(adArr, vDom);
  }

  // 视频排列规则的设置
  function layoutSetting(txt) {
    const value = GM_getValue("setting_videoNumRule");
    const newValue = prompt(
      txt.videoNumRule,
      value === undefined || value === null ? base_videoNumRule : value
    );
    GM_setValue("setting_videoNumRule", newValue || value);
    videoNumRule = getVideoNumRule(newValue || value);
    zoomPage(); // 缩放页面
    setStyle(); // 调整视频排列
  }

  // 重置设置
  function resetSettings() {
    GM_setValue("setting_isClearAd", base_isClearAd);
    GM_setValue("setting_isTrueEnd", base_isTrueEnd);
    GM_setValue("setting_isAutoLayout", base_isAutoLayout);
    GM_setValue("setting_isLoadOne", base_isLoadOne);
    GM_setValue("setting_isCarousel", base_isCarousel);
    GM_setValue("setting_videoNumRule", base_videoNumRule);
    GM_setValue("setting_delClassArr", base_delClassArr);
    GM_setValue(info.errKeyInfo.errNum, 0);
    info.errKeyArr.forEach((key) => {
      key && (key = "_" + key);
      GM_setValue(info.errKeyInfo.disNum + key, 0); // 重置
    });
    history.go(0); // 刷新页面
  }

  // 插件菜单
  function registerMenu() {
    GM_registerMenuCommand("基础设置", () => {
      basicSetting(settingText);
    });
    GM_registerMenuCommand("屏蔽设置", () => {
      delSetting(settingText);
    });
    GM_registerMenuCommand("排列规则", () => {
      layoutSetting(settingText);
    });
    GM_registerMenuCommand("重置设置", () => {
      resetSettings();
    });
  }
})();