|
| 1 | +/* SPDX-License-Identifier: BSD-2-Clause */ |
| 2 | + |
| 3 | +const personalLabelName = "mattst88" |
| 4 | +const unprocessedLabels = ["freedesktop"]; |
| 5 | +const maxThreads = 240; |
| 6 | + |
| 7 | +let personalLabel; |
| 8 | +function processInbox() { |
| 9 | + updateUserLabels(); |
| 10 | + personalLabel = GmailApp.getUserLabelByName(personalLabelName); |
| 11 | + |
| 12 | + for (const label of unprocessedLabels) { |
| 13 | + processLabel(userLabels[label]); |
| 14 | + } |
| 15 | +} |
| 16 | + |
| 17 | +let toInboxThreads = []; |
| 18 | +let toRemoveThreads = new Map(); |
| 19 | +let toAddThreads = new Map(); |
| 20 | + |
| 21 | +function processLabel(unprocessedLabel) { |
| 22 | + let threads = GmailApp.search("label:" + unprocessedLabel.getName(), 0, maxThreads); |
| 23 | + if (threads.length < 1) { |
| 24 | + Logger.log("No threads to process with label:" + unprocessedLabel.getName()); |
| 25 | + return; |
| 26 | + } else { |
| 27 | + Logger.log("Processing threads with label:" + unprocessedLabel.getName()); |
| 28 | + } |
| 29 | + |
| 30 | + for (const i in threads) { |
| 31 | + Logger.log("Processing thread " + i); |
| 32 | + |
| 33 | + processThread(unprocessedLabel, threads[i]); |
| 34 | + } |
| 35 | + |
| 36 | + /* the GmailLabel.addToThreads/removeFromThreads functions |
| 37 | + * only process 100 threads at a time */ |
| 38 | + const chunk_size = 100; |
| 39 | + |
| 40 | + /* Apply labels to threads */ |
| 41 | + for (const [label, threads] of toAddThreads) { |
| 42 | + Logger.log("Adding " + label.getName() + " label to " + threads.length + " threads"); |
| 43 | + |
| 44 | + for (let i = 0; i < threads.length; i += chunk_size) { |
| 45 | + const end = Math.min(i+chunk_size, threads.length); |
| 46 | + label.addToThreads(threads.slice(i, end)); |
| 47 | + Logger.log("\t... " + end + " done"); |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + /* Move threads to Inbox */ |
| 52 | + Logger.log("Moving " + toInboxThreads.length + " to Inbox"); |
| 53 | + for (let i = 0; i < toInboxThreads.length; i += chunk_size) { |
| 54 | + const end = Math.min(i+chunk_size, toInboxThreads.length); |
| 55 | + GmailApp.moveThreadsToInbox(toInboxThreads.slice(i, end)); |
| 56 | + Logger.log("\t... " + end + " done") |
| 57 | + } |
| 58 | + |
| 59 | + /* Remove labels from threads */ |
| 60 | + for (const label of [personalLabel, unprocessedLabel]) { |
| 61 | + const threads = toRemoveThreads.get(label); |
| 62 | + Logger.log("Removing " + label.getName() + " label from " + threads.length + " threads"); |
| 63 | + |
| 64 | + for (let i = 0; i < threads.length; i += chunk_size) { |
| 65 | + const end = Math.min(i+chunk_size, threads.length); |
| 66 | + label.removeFromThreads(threads.slice(i, end)); |
| 67 | + Logger.log("\t... " + end + " done"); |
| 68 | + } |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +function processThread(unprocessedLabel, thread) { |
| 73 | + let moveToInbox = false; |
| 74 | + let labelNames = []; |
| 75 | + |
| 76 | + let messages = thread.getMessages(); |
| 77 | + for (const message of messages) { |
| 78 | + /* If the X-GitLab-NotificationReason header exists in any message |
| 79 | + * in the thread, it was sent to us because we were mentioned, we participated, etc. |
| 80 | + * We want to move those threads to the Inbox. */ |
| 81 | + let notificationReason = message.getHeader("X-GitLab-NotificationReason") |
| 82 | + if (notificationReason) { |
| 83 | + moveToInbox = true; |
| 84 | + } |
| 85 | + |
| 86 | + /* Push the project path to a list. We'll deduplicate later. */ |
| 87 | + const projectPath = message.getHeader("X-GitLab-Project-Path"); |
| 88 | + if (projectPath) { |
| 89 | + labelNames.push(projectPath); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + /* Deduplicate labels list */ |
| 94 | + labelNames = labelNames.filter(onlyUnique); |
| 95 | + |
| 96 | + for (const labelName of labelNames) { |
| 97 | + /* Get/create a label nested under our unprocessed label */ |
| 98 | + const label = getLabel(unprocessedLabel.getName() + "/" + labelName); |
| 99 | + |
| 100 | + if (!toAddThreads.has(label)) { |
| 101 | + toAddThreads.set(label, []); |
| 102 | + } |
| 103 | + toAddThreads.get(label).push(thread); |
| 104 | + } |
| 105 | + |
| 106 | + if (moveToInbox) { |
| 107 | + toInboxThreads.push(thread); |
| 108 | + } else { |
| 109 | + if (!toRemoveThreads.has(personalLabel)) { |
| 110 | + toRemoveThreads.set(personalLabel, []); |
| 111 | + } |
| 112 | + toRemoveThreads.get(personalLabel).push(thread); |
| 113 | + } |
| 114 | + |
| 115 | + if (!toRemoveThreads.has(unprocessedLabel)) { |
| 116 | + toRemoveThreads.set(unprocessedLabel, []); |
| 117 | + } |
| 118 | + toRemoveThreads.get(unprocessedLabel).push(thread); |
| 119 | +} |
| 120 | + |
| 121 | +/* Makes a hash table of "name" -> label */ |
| 122 | +function makeNameToLabelTbl(labels) { |
| 123 | + let table = []; |
| 124 | + for (const label of labels) { |
| 125 | + table[label.getName()] = label; |
| 126 | + } |
| 127 | + return table; |
| 128 | +} |
| 129 | + |
| 130 | +/* Cache of user labels, indexed by name string */ |
| 131 | +let userLabels = []; |
| 132 | +function updateUserLabels() { |
| 133 | + userLabels = makeNameToLabelTbl(GmailApp.getUserLabels()); |
| 134 | +} |
| 135 | + |
| 136 | +/* Returns a GmailLabel given a name string. |
| 137 | + * If it doesn't exist, it creates it. */ |
| 138 | +function getLabel(name) { |
| 139 | + let label; |
| 140 | + if (!userLabels[name]) { |
| 141 | + label = GmailApp.createLabel(name); |
| 142 | + updateUserLabels(); |
| 143 | + } else { |
| 144 | + label = userLabels[name]; |
| 145 | + } |
| 146 | + return label; |
| 147 | +} |
| 148 | + |
| 149 | +function onlyUnique(value, index, self) { |
| 150 | + return self.indexOf(value) === index; |
| 151 | +} |
0 commit comments