blob: 253fe8fcd9fad09c539b817e645d7b4a7b13854e [file] [log] [blame]
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +02001import {CCApi} from '../../common/api.js';
2import {parseUrl} from '../../common/commonUtils.js';
3
4export var avatars = {
5 isFilterSetUp: false,
6 privateForums: [],
7
8 // Gets a list of private forums. If it is already cached, the cached list is
9 // returned; otherwise it is also computed and cached.
10 getPrivateForums() {
11 return new Promise((resolve, reject) => {
12 if (this.isFilterSetUp) return resolve(this.privateForums);
13
14 if (!document.documentElement.hasAttribute('data-startup'))
15 return reject('[threadListAvatars] Couldn\'t get startup data.');
16
17 var startupData =
18 JSON.parse(document.documentElement.getAttribute('data-startup'));
19 var forums = startupData?.['1']?.['2'];
20 if (forums === undefined)
21 return reject(
22 '[threadListAvatars] Couldn\'t retrieve forums from startup data.');
23
24 for (var f of forums) {
25 var forumId = f?.['2']?.['1']?.['1'];
26 var forumVisibility = f?.['2']?.['18'];
27 if (forumId === undefined || forumVisibility === undefined) {
28 console.warn(
29 '[threadListAvatars] Coudln\'t retrieve forum id and/or forum visibility for the following forum:',
30 f);
31 continue;
32 }
33
34 // forumVisibility's value 1 means "PUBLIC".
35 if (forumVisibility != 1) this.privateForums.push(forumId);
36 }
37
38 // Forum 51488989 is marked as public but it is in fact private.
39 this.privateForums.push('51488989');
40
41 this.isFilterSetUp = true;
42 return resolve(this.privateForums);
43 });
44 },
45
46 // Some threads belong to private forums, and this feature will not be able to
47 // get its avatars since it makes an anonymomus call to get the contents of
48 // the thread.
49 //
50 // This function returns whether avatars should be retrieved depending on if
51 // the thread belongs to a known private forum.
52 shouldRetrieveAvatars(thread) {
53 return this.getPrivateForums().then(privateForums => {
54 return !privateForums.includes(thread.forum);
55 });
56 },
57
58 // Get an object with the author of the thread and an array of the first |num|
59 // replies from the thread |thread|.
60 getFirstMessages(thread, num = 15) {
61 return CCApi(
62 'ViewThread', {
63 1: thread.forum,
64 2: thread.thread,
65 // options
66 3: {
67 // pagination
68 1: {
69 2: num, // maxNum
70 },
71 3: true, // withMessages
72 5: true, // withUserProfile
73 10: false, // withPromotedMessages
74 16: false, // withThreadNotes
75 18: true, // sendNewThreadIfMoved
76 }
77 },
78 // |authentication| is false because otherwise this would mark
79 // the thread as read as a side effect, and that would mark all
80 // threads in the list as read.
81 //
82 // Due to the fact that we have to call this endpoint
83 // anonymously, this means we can't retrieve information about
84 // threads in private forums.
85 /* authentication = */ false)
86 .then(data => {
87 var numMessages = data?.['1']?.['8'];
88 if (numMessages === undefined)
89 throw new Error(
90 'Request to view thread doesn\'t include the number of messages');
91
92 var messages = numMessages == 0 ? [] : data?.['1']['3'];
93 if (messages === undefined)
94 throw new Error(
95 'numMessages was ' + numMessages +
96 ' but the response didn\'t include any message.');
97
98 var author = data?.['1']?.['4'];
99 if (author === undefined)
100 throw new Error(
101 'Author isn\'t included in the ViewThread response.');
102
103 return {
104 messages,
105 author,
106 };
107 });
108 },
109
110 // Get a list of at most |num| avatars for thread |thread|
111 getVisibleAvatars(thread, num = 3) {
112 return this.shouldRetrieveAvatars(thread).then(shouldRetrieve => {
113 if (!shouldRetrieve) {
114 console.debug('[threadListAvatars] Skipping thread', thread);
115 return [];
116 }
117
118 return this.getFirstMessages(thread).then(result => {
119 var messages = result.messages;
120 var author = result.author;
121
122 var avatarUrls = [];
123
124 var authorUrl = author?.['1']?.['2'];
125 if (authorUrl !== undefined) avatarUrls.push(authorUrl);
126
127 for (var m of messages) {
128 var url = m?.['3']?.['1']?.['2'];
129
130 if (url === undefined) continue;
131 if (!avatarUrls.includes(url)) avatarUrls.push(url);
132 if (avatarUrls.length == 3) break;
133 }
134
135 return avatarUrls;
136 });
137 });
138 },
139
140 // Inject avatars for thread summary (thread item) |node| in a thread list.
141 inject(node) {
142 var header = node.querySelector(
143 'ec-thread-summary .main-header .panel-description a.header');
144 if (header === null) {
145 console.error(
146 '[threadListAvatars] Header is not present in the thread item\'s DOM.');
147 return;
148 }
149
150 var thread = parseUrl(header.href);
151 if (thread === false) {
152 console.error('[threadListAvatars] Thread\'s link cannot be parsed.');
153 return;
154 }
155
156 this.getVisibleAvatars(thread)
157 .then(avatarUrls => {
158 var avatarsContainer = document.createElement('div');
159 avatarsContainer.classList.add('TWPT-avatars');
160
161 var count = Math.floor(Math.random() * 4);
162
163 for (var i = 0; i < avatarUrls.length; ++i) {
164 var avatar = document.createElement('div');
165 avatar.classList.add('TWPT-avatar');
166 avatar.style.backgroundImage = 'url(\'' + avatarUrls[i] + '\')';
167 avatarsContainer.appendChild(avatar);
168 }
169
170 header.appendChild(avatarsContainer);
171 })
172 .catch(err => {
173 console.error(
174 '[threadListAvatars] Could not retrieve avatars for thread',
175 thread, err);
176 });
177 },
178};