Camamba Chat Helpers

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

当前为 2021-05-24 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/423722/934131/Camamba%20Chat%20Helpers.js

  1. // ==UserScript==
  2. // @name Camamba Chat Helpers
  3. // @namespace dannysaurus.camamba
  4. // @version 0.2.0
  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.id;
  23. };
  24.  
  25. const toArray = () => {
  26. if (Array.isArray(users)) {
  27. return [...users];
  28. }
  29.  
  30. if (users.id && users.name) {
  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. // { nick = '', gender = User.GENDERS.ANY, isOnline = false, hasPicture = false, isPremium = false, isReal = false, isSuper = false, page = 0 } = {}
  158. const add = (name, byNameExact = false) => {
  159. UserSearch.execute({ nick: name }).then(searchResult => searchResult.forEach(u => {
  160. if (!users[u.uid] && (!byNameExact || users[u.uid].name === u.name)) {
  161.  
  162. users[u.uid] = {
  163. id: u.uid,
  164. name: u.nick,
  165. age: u.age,
  166. level: u.userLevel,
  167. real: u.isReal ? 1 : 0,
  168. premium: (() => {
  169. if (u.isSuper) {
  170. return 3;
  171. }
  172. if (u.isPremium){
  173. return 1;
  174. }
  175. return 0;
  176. })(),
  177. gender: (() => {
  178. if (u.gender === User.GENDERS.MALE) {
  179. return "male";
  180. }
  181. if (u.gender === User.GENDERS.FEMALE) {
  182. return "female";
  183. }
  184. if (u.gender === User.GENDERS.COUPLE) {
  185. return "couple";
  186. }
  187. return "";
  188. })()
  189. };
  190. }
  191. }));
  192. };
  193.  
  194. const userPropertiesDescriptor = Object.fromEntries([
  195. "age",
  196. "audio",
  197. "blocked",
  198. "cam",
  199. "camWantPending",
  200. "camWantProcessed",
  201. "friend",
  202. "gender",
  203. "id",
  204. "initPriv",
  205. "level",
  206. "moderator",
  207. "name",
  208. "premium",
  209. "real",
  210. "requestDeAnnoy",
  211. "video",
  212. "viewing"
  213. ].map(propName => ([ propName, {
  214. get() {
  215. return toArray().map(user => user[propName]);
  216. },
  217. configurable: true
  218. }])));
  219.  
  220. const metaPropertiesDescriptor = Object.fromEntries(Object.entries({
  221. excluded: users.excluded || [],
  222. excludedAll: users.excludedAll || [],
  223. toArray,
  224. toString,
  225.  
  226. by,
  227.  
  228. byId,
  229. bySelected,
  230. byName,
  231. byGender,
  232. byPos,
  233. byIsCammed,
  234. byIsNotCammed: () => byIsCammed().excluded,
  235. byViewing,
  236.  
  237. add,
  238.  
  239. stopViewing,
  240.  
  241. ban,
  242. ban24: (text, config = { isPublic: false, isPerma: false, suppressBanLog: false }) => ban(text, 24, config),
  243. banCausePermaDe: () => ban("Du bist dauerhaft gebannt. Bitte erstelle keine weiteren Konten!", 24),
  244. banCausePermaEn: () => ban("You are permanently banned from Camamba. Please do not create any additional accounts!", 24),
  245. banPerma: (text) => ban(text, 24, { isPublic: false, isPerma: true, suppressBanLog: false }),
  246.  
  247. banSilent: (text, time) => ban(text, time, { isPublic: false, isPerma: false, suppressBanLog: true }),
  248. banSilentPerma: (text, time) => ban(text, time, { isPublic: false, isPerma: true, suppressBanLog: true }),
  249.  
  250. save,
  251. restore
  252. }).map(([propName, value]) => {
  253. return [propName, { value, configurable: true }];
  254. }));
  255.  
  256. return Object.defineProperties(users, { ...userPropertiesDescriptor, ...metaPropertiesDescriptor });
  257. }
  258.  
  259. function decorateRooms(rooms = {}) {
  260. const roomsByName = (name) => {
  261. const nameLower = String(name).toLowerCase();
  262.  
  263. const result = {};
  264.  
  265. Object.entries(rooms).forEach(([roomId, roomName]) => {
  266.  
  267. if (roomName.toLowerCase().includes(nameLower)) {
  268. result[roomId] = roomName;
  269. }
  270. });
  271.  
  272. return result;
  273. };
  274.  
  275. return Object.defineProperties(rooms, {
  276. byName: { value: roomsByName, configurable: true },
  277. });
  278. }
  279.  
  280. const patchObject = ({ getExpected, doPatch, confirmAvailable = null, timeOutRetryMillis = 200, maxPeriodTryMillis = 5000 }) => {
  281. const expected = getExpected();
  282. const isAvailable = confirmAvailable ? confirmAvailable(expected) : !!expected;
  283.  
  284. if (!isAvailable) {
  285. if (timeOutRetryMillis <= maxPeriodTryMillis) {
  286.  
  287. setTimeout(() => {
  288. maxPeriodTryMillis -= timeOutRetryMillis;
  289. patchObject({ getExpected, doPatch, confirmAvailable, timeOutRetryMillis, maxPeriodTryMillis });
  290.  
  291. }, timeOutRetryMillis);
  292. }
  293.  
  294. return;
  295. }
  296.  
  297. doPatch(expected);
  298. };
  299.  
  300. patchObject({
  301. getExpected: () => {
  302. return knownUsers;
  303. },
  304. doPatch: (users) => {
  305. decorateUsers(users);
  306. }
  307. });
  308.  
  309. patchObject({
  310. getExpected: () => {
  311. return rooms;
  312. },
  313. doPatch: (rooms) => {
  314. decorateRooms(rooms);
  315. }
  316. });
  317. })();