Skip to content

Instantly share code, notes, and snippets.

@raisedadead
Last active October 15, 2023 18:35
Show Gist options
  • Save raisedadead/27c335bd879725c72e54a08d6b99a0b5 to your computer and use it in GitHub Desktop.
Save raisedadead/27c335bd879725c72e54a08d6b99a0b5 to your computer and use it in GitHub Desktop.
Delete Spam Emails from Gmail — using Google App Script.

A Google App Script to detele emails from the spam folder. Why? – Because that's what bored programmers do. This is a fork of https://github.com/spamzero/spamzero, with minor updates to suite my needs. In case you use this please check and update the rules. Goodluck.

// version 1.0.0
// Forked from https://github.com/spamzero/spamzero with some improvements.
/**
* The threads you want to process. A thread (GmailThread) consists
* of multiple messages (GmailMessage).
* See: https://developers.google.com/apps-script/reference/gmail/
*/
var threads = GmailApp.getSpamThreads();
/**
* The action(s) to take on the threads matching your rules.
*
* Possible values are: READ, MOVE_TO_TRASH, LABEL(name).
* You can also use multiple actions ["READ", "LABEL(Some label)"].
*/
var actions = ['MOVE_TO_TRASH'];
var rules = [
// This first one should be the first rule in the ruleset
function (m, raw) {
'No subject in the headers'
if (!raw.headers.subject) {
return true;
}
},
function (m, raw) {
'Goomoji + name in subject'
return StringHelper.containsGoomoji(raw.headers.subject);
},
function (m, raw) {
'From *.ru + image attachment'
var ruSender = m.getFrom().match(/\.ru>?$/);
var withImageAttachment = MessageHelper.hasImageAttachment(m);
return ruSender && withImageAttachment;
},
function (m, raw) {
'Profanity filter'
var matchers = [
/slut/i,
/f.ck/i,
/s.ck/i,
/c.ck/i,
/lady/i,
/h..kup/i,
/cum/i,
/babe/i,
/p.rn/i,
/s[e3\*\.]xy?/i,
/house\s?wife/i
];
for (var i = 0; i < matchers.length; i++) {
if (
raw.headers.subject.match(matchers[i]) ||
raw.body.match(matchers[i]) ||
raw.headers.from.match(matchers[i])
) {
return true;
}
}
return false;
},
function (m, raw) {
'Has link to dropbox in body'
return raw.body.match(/dl\.dropboxusercontent\.com/i);
},
function (m, raw) {
'Shortened url in body'
var matchers = [/bit\.ly/i, /tinyurl\.com/i, /goo\.gl/];
for (var i = 0; i < matchers.length; i++) {
if (raw.body.match(matchers[i])) {
return true;
}
}
return false;
},
function (m, raw) {
'From: *.club, *.date, *.top, etc.'
return m.getFrom().match(/(\.club|\.date|\.top|\.xyz|\.trade|\.party)>?$/i);
},
function (m, raw) {
'URL to *.club, *.date, *.top, etc.'
return raw.body.match(
/(https?|www).+(\.club|\.date|\.top|\.xyz|\.trade|\.party)/i
);
},
function (m, raw) {
"URL to a .php site & 'confidential' / 'broken' words in body"
return (
raw.body.match(/(https?|www).+\.php/i) &&
raw.body.match(/(confidential|broken)/i)
);
}
];
// Debug mode (will log events, use the log viewer [View / Show logs] to view them)
var isDebug = true;
// Use these helper methods in your rules
var MessageHelper = {
hasImageAttachment: function (message) {
var attachments = message.getAttachments();
for (var i = 0; i < attachments.length; i++) {
if (attachments[i].getContentType().match(/image/)) {
return true;
}
}
return false;
}
};
var StringHelper = {
containsGoomoji: function (string) {
return string.match(/=\?[Uu][Tt][Ff]-8\?[BQ]\?[^\?]+\?=/i);
}
};
// Probably you won't need to edit the code below
var getLogger = function () {
if (isDebug) {
return Logger;
} else {
return {
clear: function () {},
log: function () {},
getLog: function () {}
};
}
};
/**
* Checks whether the given thread's messages matches any of your rules.
* Only one message needs to be matched by your rules for this function to return true.
*/
var anyMessageMatchesAnyRuleInThread = function (thread) {
var messages = thread.getMessages();
for (var i = 0; i < messages.length; i++) {
var raw = parseRawContent(messages[i].getRawContent());
for (j = 0; j < rules.length; j++) {
if (rules[j](messages[i], raw)) {
var ruleDescription = parseRuleDescription(rules[j]);
getLogger().log(
'"' +
messages[i].getSubject() +
'" matched rule "' +
(ruleDescription ? ruleDescription : j) +
'"'
);
return true;
}
}
}
return false;
};
/**
* Parses the raw message into an object. For the returned object's format see the rule array's description.
*/
var parseRawContent = function (rawContent) {
var lines = rawContent.split('\n');
var result = {};
var headers = {};
var body = '';
var currentHeaderKey = null;
var headerParsed = false;
for (var i = 0; i < lines.length; i++) {
if (lines[i].trim() === '') {
if (headers.date === undefined) {
continue;
}
headerParsed = true;
continue;
}
if (!headerParsed) {
var headerParts = lines[i].match(/^([-a-z]+):(.*)/i);
if (headerParts) {
currentHeaderKey = headerParts[1].toLowerCase();
headers[currentHeaderKey] = headerParts[2].trim();
} else {
// Header continues on new line
headers[currentHeaderKey] += ' ' + lines[i].trim();
}
} else {
body += lines[i];
}
}
if (headers['content-transfer-encoding'] === 'base64') {
try {
body = Utilities.newBlob(Utilities.base64Decode(body)).getDataAsString();
} catch (err) {
getLogger().log('Could not base64 decode body.');
}
}
result.headers = headers;
result.body = body;
return result;
};
/**
* Extracts the description from a rule.
*/
var parseRuleDescription = function (func) {
var lines = func.toString().split('\n');
for (var i = 0; i < lines.length; i++) {
var matches = lines[i].trim().match(/^"([^"]+)";?$/);
if (matches) {
return matches[1];
}
}
return '';
};
var ReadAction = function () {
this.run = function (thread) {
thread.markRead();
};
};
var MoveToTrashAction = function () {
this.run = function (thread) {
thread.moveToTrash();
};
};
var LabelAction = function (labelName) {
this.run = function (thread) {
var label = GmailApp.getUserLabelByName(labelName);
if (!label) {
label = GmailApp.createLabel(labelName);
}
thread.addLabel(label);
};
};
var ActionFactory = {
create: function (actionName) {
if (actionName === 'READ') {
return new ReadAction();
}
if (actionName === 'MOVE_TO_TRASH') {
return new MoveToTrashAction();
}
var labelParts = actionName.match(/LABEL\((.+)\)/i);
if (labelParts) {
return new LabelAction(labelParts[1]);
}
throw 'Unknown action: ' + actionName;
}
};
var runActionsOnThread = function (thread) {
for (var i = 0; i < actions.length; i++) {
var action = ActionFactory.create(actions[i]);
getLogger().log('Running action "' + actions[i] + '"');
action.run(thread);
}
};
/**
* The main function. This should be the function chosen in the execute dropdown
* in the script editor toolbar above.
*/
function main() {
getLogger().clear();
for (var i = 0; i < threads.length; i++) {
if (i > 0) {
getLogger().log('---');
}
getLogger().log('Processing thread number ' + i);
// Match the rules, or check if the thread even has a subject (seemingly a new hack from the spammers)
if (anyMessageMatchesAnyRuleInThread(threads[i]) || !threads[i].subject) {
runActionsOnThread(threads[i]);
} else {
getLogger().log(
'No action taken because none of your rules were matching this thread.'
);
getLogger().log('Subject:' + threads[i].subject);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment