Camamba Chat Helpers Library

decorates "knownUsers" and "rooms" objects with functions useful for console and other scripts

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/423722/960246/Camamba%20Chat%20Helpers%20Library.js

  1. // ==UserScript==
  2. // @name Camamba Chat Helpers Library
  3. // @namespace dannysaurus.camamba
  4. // @version 0.2.2
  5. // @description decorates "knownUsers" and "rooms" objects with functions useful for console and other scripts
  6. // @license MIT License
  7. // @include https://www.camamba.com/chat/
  8. // @include https://www.de.camamba.com/chat/
  9. // @include https://www.camamba.com/chat/
  10. // @include https://www.de.camamba.com/chat/
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. /* jslint esversion: 9 */
  15. /* global me, camData, rooms, blockList, friendList, friendRequests, adminMessages, jsLang, byId, myRooms, knownUsers, activeRoom, selectedUser, settings, onMessageHandlers, postMessageHandlers, janusSend, wsSend, activeMainRoom, postToSite */
  16. /* globals SimpleCache, HttpRequest, HttpRequestHtml, Enum, User, UserSearch, $, jQuery */
  17.  
  18. (function() {
  19. function decorateUsers(users = {}) {
  20.  
  21. const isUser = (user) => {
  22. return user.hasOwnProperty("id") && !Object.getOwnPropertyDescriptor(user, "id").get;
  23. };
  24.  
  25. const toArray = () => {
  26. if (Array.isArray(users)) {
  27. return [...users];
  28. }
  29. // single user object
  30. if (isUser(users)) {
  31. return [ users ];
  32. }
  33.  
  34. return Object.values(users);
  35. };
  36.  
  37. const toString = () => {
  38. return toArray().map(u => {
  39.  
  40. return Object.entries(u)
  41. .map(([prop, val]) => prop + ':' + val)
  42. .join('\t');
  43.  
  44. }).join('\n');
  45. };
  46.  
  47. const by = (userPredicateFnc) => {
  48. const result = [], excluded = [];
  49.  
  50. Object.values(users).filter(u => isUser(u)).forEach(u => {
  51. if(userPredicateFnc(u)) {
  52. result.push(u);
  53. } else {
  54. excluded.push(u);
  55. }
  56. });
  57.  
  58. if (excluded.length) {
  59. result.excluded = decorateUsers(excluded);
  60. result.excludedAll = decorateUsers([ ...excluded, ...users.excludedAll ]);
  61. }
  62.  
  63. return decorateUsers(result);
  64. };
  65.  
  66. const byId = (id) => {
  67. return by(user => user.id == id);
  68. };
  69.  
  70. const byName = (name) => {
  71. const nameLower = String(name).toLowerCase();
  72. return by(user => {
  73. return user.name.toLowerCase().includes(nameLower);
  74. });
  75. };
  76.  
  77. const byGender = (gender) => {
  78. const genderLower = String(gender).toLowerCase();
  79. return by(user => {
  80. return user.gender.toLowerCase().startsWith(genderLower);
  81. });
  82. };
  83.  
  84. const bySelected = () => {
  85. const selectedUserId = selectedUser ? selectedUser.dataset.id : 0;
  86.  
  87. if (!selectedUserId) {
  88. return by(user => false);
  89. }
  90.  
  91. return byId(selectedUserId);
  92. };
  93.  
  94. const byIsCammed = () => {
  95. if (!camData) return false;
  96.  
  97. const camDataUserIds = new Set(
  98. Object.values(camData)
  99. .filter(cd => cd.user)
  100. .map(cd => String(cd.user))
  101. );
  102.  
  103. return by(user => {
  104. return camDataUserIds.has(String(user.id));
  105. });
  106. };
  107.  
  108. const byViewing = () => {
  109. return users.by(user => {
  110. return user.viewing;
  111. });
  112. };
  113.  
  114. const byPos = (pos) => {
  115. return toArray()[pos];
  116. };
  117.  
  118. const stopViewing = () => {
  119. return byViewing().forEach(user => {
  120. janusSend('remove', user.id);
  121. });
  122. };
  123.  
  124. const save = () => toArray().forEach(user => {
  125. user.original = {...user};
  126. });
  127.  
  128. const restore = () => by(user => user.original).forEach(user => {
  129. Object.assign(user, user.original);
  130. delete user.original;
  131. });
  132.  
  133. const ban = (text, time, config = { isPublic: false, isPerma: false, suppressBanLog: false }) => {
  134. const { isPublic, isPerma, suppressBanLog } = config;
  135.  
  136. if (me.admin && toArray(users).length === 1) {
  137. const currentAdminTarget = users[0].id;
  138. // ban
  139. wsSend( { command: "ban", target: currentAdminTarget, reason: text, time: time * 3600 } );
  140. // notify user
  141. wsSend( { command: "admin", type: "admin", msg: { text: knownUsers[me.id].name + " banned " + knownUsers[currentAdminTarget].name+" for "+time+" hours.", room: activeMainRoom, notify: true }} );
  142. // to banlog
  143. if (!suppressBanLog) {
  144. postToSite("/adm_banned.php", "duration="+time+"&myname="+escape(knownUsers[me.id].name)+"&banname="+escape(knownUsers[currentAdminTarget].name)+"&roomname="+escape(activeMainRoom)+"&reason="+escape(text));
  145. }
  146. // to chat
  147. if (isPublic) {
  148. wsSend( { command: "admin", type: "room", msg: { text: knownUsers[currentAdminTarget].name+" has been banned.", room: activeMainRoom }} );
  149. }
  150. // do perma
  151. if (isPerma) {
  152. postToSite("/adm_set.php", "uID="+currentAdminTarget+"&ban=-1");
  153. }
  154. }
  155. };
  156.  
  157. const banPerma = (text) => ban(text, 24, { isPublic: false, isPerma: true, suppressBanLog: false });
  158.  
  159. // { nick = '', gender = User.GENDERS.ANY, isOnline = false, hasPicture = false, isPremium = false, isReal = false, isSuper = false, page = 0 } = {}
  160. const add = (name, byNameExact = false) => {
  161. return UserSearch.execute({ nick: name }).then(searchResult => {
  162. if (byNameExact) {
  163. searchResult = searchResult.filter(u => u.nick === name);
  164. }
  165.  
  166. searchResult.forEach(u => {
  167. users[u.uid] = {
  168. id: u.uid,
  169. name: u.nick,
  170. age: u.age,
  171. level: u.userLevel,
  172. real: u.isReal ? 1 : 0,
  173. premium: (() => {
  174. if (u.isSuper) {
  175. return 3;
  176. }
  177. if (u.isPremium){
  178. return 1;
  179. }
  180. return 0;
  181. })(),
  182. gender: (() => {
  183. if (u.gender === User.GENDERS.MALE) {
  184. return "male";
  185. }
  186. if (u.gender === User.GENDERS.FEMALE) {
  187. return "female";
  188. }
  189. if (u.gender === User.GENDERS.COUPLE) {
  190. return "couple";
  191. }
  192. return "";
  193. })(),
  194. };
  195. console.log(`found user with uid ${u.uid}, username ${u.nick}, level ${u.userLevel}`);
  196. });
  197.  
  198. return searchResult;
  199. });
  200. };
  201.  
  202. const banPermaByName = (name, text) => {
  203. // fast ban
  204. byName(name).banPermaFast(text);
  205.  
  206. // via user search
  207. add(name, true).then(searchResult => {
  208. // only permaban new users and with an exact unique search result
  209. const uid = (searchResult.length === 1) ? searchResult[0].uid : -1;
  210. if (uid > 1279501) {
  211. byId(uid).banPermaFast(text);
  212. }
  213. });
  214. };
  215.  
  216. const userPropertiesDescriptor = Object.fromEntries([
  217. "age",
  218. "audio",
  219. "blocked",
  220. "cam",
  221. "camWantPending",
  222. "camWantProcessed",
  223. "friend",
  224. "gender",
  225. "id",
  226. "initPriv",
  227. "level",
  228. "moderator",
  229. "name",
  230. "premium",
  231. "real",
  232. "requestDeAnnoy",
  233. "video",
  234. "viewing",
  235. ].map(propName => ([ propName, {
  236. get() {
  237. return Object.values(users)
  238. .filter(u => isUser(u))
  239. .map(u => u[propName]);
  240. },
  241. configurable: true,
  242. }])));
  243.  
  244. const metaPropertiesDescriptor = Object.fromEntries(Object.entries({
  245. excluded: users.excluded || [],
  246. excludedAll: users.excludedAll || [],
  247. toArray,
  248. toString,
  249.  
  250. by,
  251.  
  252. byId,
  253. bySelected,
  254. byName,
  255. byGender,
  256. byPos,
  257. byIsCammed,
  258. byIsNotCammed: () => byIsCammed().excluded,
  259. byViewing,
  260.  
  261. add,
  262. addExact: (name) => add(name, true),
  263.  
  264. stopViewing,
  265.  
  266. ban,
  267. ban24: (text, config = { isPublic: false, isPerma: false, suppressBanLog: false }) => ban(text, 24, config),
  268. banPerma: (name) => {
  269. const text = "Du bist dauerhaft gebannt. Bitte erstelle keine weiteren Konten!";
  270. if (typeof name === "undefined" || !name.length) {
  271. banPerma(text);
  272. } else {
  273. banPermaByName(name, text);
  274. }
  275. },
  276. banPermaEn: (name) => {
  277. const text = "You are permanently banned from Camamba. Please do not create any additional accounts!";
  278. if (typeof name === "undefined" || !name.length) {
  279. banPerma(text);
  280. } else {
  281. banPermaByName(name, text);
  282. }
  283. },
  284. banPermaFast: (text) => banPerma(text),
  285.  
  286. banSilent: (text, time) => ban(text, time, { isPublic: false, isPerma: false, suppressBanLog: true }),
  287. banSilentPerma: (text, time) => ban(text, time, { isPublic: false, isPerma: true, suppressBanLog: true }),
  288.  
  289. save,
  290. restore,
  291. }).map(([propName, value]) => {
  292. return [propName, { value, configurable: true }];
  293. }));
  294.  
  295. return Object.defineProperties(users, { ...userPropertiesDescriptor, ...metaPropertiesDescriptor });
  296. }
  297.  
  298. function decorateRooms(rooms = {}) {
  299. const roomsByName = (name) => {
  300. const nameLower = String(name).toLowerCase();
  301.  
  302. const result = {};
  303.  
  304. Object.entries(rooms).forEach(([roomId, roomName]) => {
  305.  
  306. if (roomName.toLowerCase().includes(nameLower)) {
  307. result[roomId] = roomName;
  308. }
  309. });
  310.  
  311. return result;
  312. };
  313.  
  314. return Object.defineProperties(rooms, {
  315. byName: { value: roomsByName, configurable: true },
  316. });
  317. }
  318.  
  319. const patchObject = ({ getExpected, doPatch, confirmAvailable = null, timeOutRetryMillis = 200, maxPeriodTryMillis = 5000 }) => {
  320. const expected = getExpected();
  321. const isAvailable = confirmAvailable ? confirmAvailable(expected) : !!expected;
  322.  
  323. if (!isAvailable) {
  324. if (timeOutRetryMillis <= maxPeriodTryMillis) {
  325.  
  326. setTimeout(() => {
  327. maxPeriodTryMillis -= timeOutRetryMillis;
  328. patchObject({ getExpected, doPatch, confirmAvailable, timeOutRetryMillis, maxPeriodTryMillis });
  329.  
  330. }, timeOutRetryMillis);
  331. }
  332.  
  333. return;
  334. }
  335.  
  336. doPatch(expected);
  337. };
  338.  
  339. patchObject({
  340. getExpected: () => {
  341. return knownUsers;
  342. },
  343. doPatch: (users) => {
  344. decorateUsers(users);
  345. }
  346. });
  347.  
  348. patchObject({
  349. getExpected: () => {
  350. return rooms;
  351. },
  352. doPatch: (rooms) => {
  353. decorateRooms(rooms);
  354. }
  355. });
  356. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址