Columns for Electronic Mail Format

Overview:

This script adds columns that extract values from MIME headers (which are used for email and webpage files). It also adds commands that leverage these values.

Based on this previous script. I believe this version has some improvements:

  • caching of values;
  • commands;

Example of Usage


Columns for Electronic Mail: "Date", "From Address", "Message-ID", "Newsgroups", "Subject", "To Address".

Columns for Webpages: "Address", "Domain", "Source", "Title", "Visualized On".

Commands for Webpages:

  • OpenWebpage: depending on the path of the selected item, opens the page in the default browser or in another user-specified browser. Let's the user choose between opening the saved file or opening the original URL.

Installation:

  • Download Columns for Electronic Mail Format.js.txt (13.7 KB)
  • Drag it to Preferences / Toolbars / Scripts.
  • Create needed file type groups manually.
    • "Webpages" - relevant extensions ".mht .mhtml .html .xht .xhtml .shtml .htm .url" (not all supported by the script)
    • "Electronic Mail" - relevant extensions ".eml .msg .rfc"
  • Suggested use of commands: set OpenWebpage as the action for double click for the file type group Webpages and OpenWebpage REMOTE as the shift+double click action.)
    • Use the script's settings to configure which browsers are used, otherwise it will use the default one.

History:

  • 1.0 (2018-10-05): Initial version.

Future Possibilities:

  • Improve support for the many variations of encoding and formatting.
  • Make a command to copy the values of the custom columns to clipboard (mainly for the remote URL).
  • Option to make the cache persist?

Script code:

The script code from the download above is reproduced below. This is for people browsing the forum for scripting techniques. You do not need to care about this code if you just want to use the script.

// -------------------- Global Variables

// Helper Objects
var fs = new ActiveXObject("Scripting.FileSystemObject");
var st = DOpus.Create.StringTools();

// Default example for the mapDirToBrowser setting.
var map_dir_to_browser = {
	"D:\\Bookmarks\\Other\\": "C:\\Bin\\Browser.exe",
};

// Columns' Definitions
var config = {
	columns: [
		{
		name: "ToAddress",
		displayName: "To Address",
		target: "mail",
		re: /^To:\s(.*)$/,
		type: "string",
		defwidth: 18
		}, {
		name: "FromAddress",
		displayName: "From Address",
		target: "mail",
		re: /^From:\s(.*)$/,
		type: "string",
		defwidth: 18
		}, {
		name: "Subject",
		displayName: "Subject",
		target: "mail",
		re: /^Subject:\s(.*)$/,
		type: "string",
		defwidth: 20
		}, {
		name: "Newsgroups",
		displayName: "Newsgroups",
		target: "mail",
		re: /^Newsgroups:\s(.*)$/,
		type: "string",
		defwidth: 18
		}, {
		name: "Message-ID",
		displayName: "Message-ID",
		target: "mail",
		re: /^Message-ID:\s(.*)$/,
		type: "string",
		defwidth: 18
		}, {
		name: "Date",
		displayName: "Date",
		target: "mail",
		re: /^Date:\s(.*)$/,
		type: "datetime",
		defwidth: 10
		}, {
		name: "Webpage Address",
		displayName: "Address",
		target: "webpage",
		re: /^Content-Location:\s(.*)$/,
		type: "string",
		defwidth: 18
		}, {
		name: "Webpage Domain",
		displayName: "Domain",
		target: "webpage",
		re: /^Content-Location:\s(?:http)s?:\/\/(?:www\.)?(.*?)\/.*$/,
		type: "string",
		defwidth: 18
		}, {
		name: "Webpage Source",
		displayName: "Source",
		target: "webpage",
		re: /^From:\s(.*)$/,
		type: "string",
		defwidth: 18
		}, {
		name: "Webpage Subject",
		displayName: "Title",
		target: "webpage",
		re: /^Subject:\s(.*)$/,
		type: "string",
		defwidth: 20
		}, {
		name: "Webpage Date",
		displayName: "Visualized On",
		target: "webpage",
		re: /^Date:\s(.*)$/,
		type: "datetime",
		defwidth: 10
		}
	]
};

// -------------------- Event

// Called by Directory Opus to initialize the script.
function OnInit(initData) {
	var uid = "";
	var url = "";
	initData.url = url;
	initData.name = "Columns for Electronic Mail Format";
	initData.desc = "Columns for displaying e-mail header properties for e-mail and webpage files.";
	initData.copyright = "Initiated by wowbagger; With contributions from many fellow users; Fork maintened by AndersonNNunes.org";
	initData.version = "1.0";
	initData.default_enable = true;
	initData.min_version = "12.0";
	
	// Create a new ScriptCommand object and initialize it to add the command to Opus.
	var showPreviewCmd = initData.AddCommand();
	showPreviewCmd.name = "OpenWebpage";
	showPreviewCmd.method = "OnOpenWebpage";
	showPreviewCmd.desc = "Open webpage files.";
	showPreviewCmd.label = "Open Webpage";
	showPreviewCmd.template = "MULTI/S,REMOTE/S";
	
	// Helper Function
	// Easy way to determine settings.
	// Thanks goes to "tbone".
	// https://resource.dopus.com/t/helper-confighelper-easier-config-item-handling/19129
	function ConfigHelper(data) {
		var t=this; t.d=data; t.c=data.config; t.cd=DOpus.Create.Map();
		t.add=function(name, val, des){ t.l={n:name,ln:name.
			toLowerCase()}; return t.val(val).des(des);}
		t.des=function(des){ if (!des) return t; if (t.cd.empty)
			t.d.config_desc=t.cd; t.cd(t.l.n)=des; return t;}
		t.val=function(val){ var l=t.l; if (l.v!==l.x&&typeof l.v=="object")
			l.v.push_back(val);else l.v=t.c[l.n]=val;return t;}
		t.trn=function(){return t.des(t("script.config."+t.l.ln));}
	}
	
	// Helper object.
	var cfg = new ConfigHelper(initData);
	
	// Configuration.
	cfg.add("verbose", true, "Enable output of informative log? If set to false, only errors will print messages.");
	cfg.add("facilitateSubjectGrouping", true, "When enabled, grouping by subject ignores certain prefixes so that all messages from a thread are grouped together.");
	cfg.add("prefixRegExp", "(RES|ENC|Re|Fwd): ", "RegExp used to identify the prefixes to be ignored if the user enabled the 'facilitateSubjectGrouping' option.");
	cfg.add("clearOutput", false, "Clear output on run?");
	cfg.add("mapDirToBrowser", JSON.stringify(map_dir_to_browser, undefined, 2).replace(/(\n)+/g,"\r\n"), "Specifies which directories have files that should be opened with a given browser. More specific paths should be at the top.");
	
	// Temporary variables used to process the script configuration.
	var labelPrefix;
	
	for (i = 0; i < config.columns.length; i++) {
		
		// Determine the prefix from the target of the column.
		if (config.columns[i].target == "mail") {
			labelPrefix = "(Mail) ";
		} else if (config.columns[i].target == "webpage") {
			labelPrefix = "(Webpage) ";
		}
		
		AddNewColumn(initData, config.columns[i].name, config.columns[i].displayName, labelPrefix, config.columns[i].type, config.columns[i].defwidth);
	}
}

function OnOpenWebpage(scriptCmdData) {
	if (Script.config.clearOutput) {
		DOpus.ClearOutput();
	}
	
	// Check premisses.
	if (scriptCmdData.func.sourcetab.selected_files.count == 0)
	{
		error("No files are selected.");
		return;
	}
	
	if (scriptCmdData.func.sourcetab.selected_files.count > 1 && !scriptCmdData.func.args.got_arg.multi) {
		error("Too many files are selected. Use the MULTI switch if you really want the command to operate on multiple files.");
		return;
	}
	else
	{
		var multi = true;
	}
	
	// Adjust command behavior.
	var cmd = scriptCmdData.func.command;
	cmd.AddLine("// This line prevents an error when the Run method is executed before the the AddLine method being called at least once.");
	cmd.deselect = true;
	cmd.SetQualifiers("none");
	cmd.ClearFiles();
	
	if (multi == true) {
		// Filter selected files to consider only appropriate files.
		for (var eSel = new Enumerator(scriptCmdData.func.sourcetab.selected_files); !eSel.atEnd(); eSel.moveNext())
		{
			var selectedItem = eSel.item();
			for (var i = 0; i < selectedItem.groups.length; i++){
				if (selectedItem.groups(i).display_name == "Webpages")
				{
					cmd.AddFile(selectedItem);
				}
			}
		}
	} else {
		cmd.AddFile(scriptCmdData.func.sourcetab.selected_files(0));
	}
	
	for (var file_count = cmd.files.count - 1; file_count >= 0; file_count--)
	{
		var current_item = cmd.files(file_count);
		var current_item_path = String(current_item.realpath);
		
		if (scriptCmdData.func.args.got_arg.remote) {
			var item_id = encodeURIComponent("_CEMF_" + current_item_path);
			if (DOpus.Vars.Exists(item_id)) {
				var item = JSON.parse(DOpus.Vars.Get(item_id));
				var target_uri = item["Webpage Address"];
			} else {
				error("Metadata not cached for \"" + current_item.name + "\".");
				return;
			}
		} else {
			var target_uri = current_item_path;
		}
		
		var browser = "default";
		map_dir_to_browser = JSON.parse(Script.config.mapDirToBrowser);
		for (key in map_dir_to_browser) {
			if (DOpus.FSUtil.ComparePath(current_item_path, key, "cp")) {
				browser = map_dir_to_browser[key];
				break;
			}
		}
		
		if (browser === "default") {
			var open_browser_command = quotePath(target_uri);
		} else {
			var open_browser_command = quotePath(browser) + " " + quotePath(target_uri);
		}
		
		// Execute command line.
		var wsh = new ActiveXObject("WScript.Shell");
		var run_return_code = wsh.Run(open_browser_command, 1);
	}
}

// Helper Function
// Add column.
function AddNewColumn(initData, name, label, labelPrefix, type, defwidth) {
	var col;
	col = initData.AddColumn();
	col.name = name;
	col.label = labelPrefix + label;
	col.header = label;
	col.type = type;
	col.method = "OnValueRequest";
	if (name == "Subject" || name == "Webpage Subject") {
		col.autogroup = false;
	} else {
		col.autogroup = true;
	}
	col.autorefresh = true;
	col.justify = "left";
	col.multicol = true;
	col.keyscroll = true;
	if (defwidth != null && defwidth != 0) {
		col.defwidth = defwidth;
	}
}

// Helper Function
// Generate values for columns.
function OnValueRequest(scriptColData) {
	// Check that the target item really exists (sometimes it may be on a collection but may have been deleted already).
	if (!DOpus.FSUtil.Exists(scriptColData.item) || (!scriptColData.item.is_dir && !fs.FileExists(scriptColData.item.realpath)) || (scriptColData.item.is_dir && !fs.FolderExists(scriptColData.item.realpath))) {
		// error("Unable to locate \"" + scriptColData.item.realpath + "\".");
		return;
	}
	
	if (String(scriptColData.item.realpath).length > 259) {
		print("Path length is too long for the file \"" + scriptColData.item.realpath + "\". It has been ignored.");
		return;
	}
	
	if (!inArray(scriptColData.col, config.columns)) return;
	if (scriptColData.item.is_dir) return;
	if (isFromGroup(scriptColData.item, "Electronic Mail") || isFromGroup(scriptColData.item, "Webpages"))
	{
		// Check the cache first.
		var item_id = encodeURIComponent("_CEMF_" + scriptColData.item.realpath);
		if (DOpus.Vars.Exists(item_id)) {
			var item = JSON.parse(DOpus.Vars.Get(item_id));
			if (item["Cache Date"] !== String(scriptColData.item.modify)) {
				item = null;
				print("Cached data is stale and will be discarded.");
			}
			print("Using 'DOpus.Vars' cache.");
		}
		
		if (!item) {
			print("Data not found in the cache. Probing...");
			var item = ScanFile(scriptColData.item);
			item["Cache Date"] = String(scriptColData.item.modify);
			DOpus.Vars.Set(item_id, JSON.stringify(item, undefined, 4).replace(/(\n)+/g,"\r\n"));
		}
		
		if (item) {
			// Temporary variable that holds a value to be assigned to a column.
			var iValue;
			
			// Iterate over columns and assign values.
			for (i = 0; i < config.columns.length; i++) {
				
				if (!scriptColData.columns.exists(config.columns[i].name)) {
					continue;
				}
				
				iValue = item[config.columns[i].name];
				
				if (iValue == null) {
					continue;
				}
				
				// Handle column of type "datetime".
				if (config.columns[i].type == "datetime") {
					iValue = new Date(Date.parse(iValue)).getVarDate();
				} // Handle other types of column.
				else {
					// For the "Subject" column, remove common prefixes of the group value, if the user desires that.
					if (config.columns[i].name == "Subject" || config.columns[i].name == "Webpage Subject") {
						if (Script.config.facilitateSubjectGrouping) {
							scriptColData.columns(config.columns[i].name).group = iValue.replace(new RegExp(Script.config.prefixRegExp, "gi"), "")
						}
					}
				}
				
				// Assign computed value.
				scriptColData.columns(config.columns[i].name).value = iValue;
			}
		}
	}
}

// Helper Function
// Extract values from file.
function ScanFile(item, target) {
	var result = {};
	
	for (i = 0; i < config.columns.length; i++)
	{
		// Check premisses; skip column if check fail.
		if (isFromGroup(item, "Electronic Mail") && config.columns[i].target == "webpage") {
			continue;
		}
		if (isFromGroup(item, "Webpages") && config.columns[i].target == "mail") {
			continue;
		}
		
		// Open file in read mode.
		var fn = fs.OpenTextFile(item.realpath, 1);
		while (!fn.AtEndOfStream)
		{
			var textLine = fn.ReadLine();
			var match = config.columns[i].re.exec(textLine);
			if (match != null)
			{
				var found_content = match[1];
				var subsequentLine = fn.ReadLine();
				while (subsequentLine.match(/^(\s|\t).*$/)) {
					found_content += subsequentLine;
					subsequentLine = fn.ReadLine();
				}
				
				result[config.columns[i].name] = decode(found_content);
				fn.Close();
				break;
			}
		result[config.columns[i].name] = "";
		}
		fn.Close();
	}
	
	if (result)
		return result;
	else
		return null;
}

// Helper Function
// Check that a given item is present in an array.
function inArray(item, arr) {
	for (var i = 0; i < arr.length; i++) {
		if (item == arr[i].name) return true;
	}
}

// Helper Function
// Decode line of text.
function decode(text) {
	var encoded_multipart_regex_pattern = /\?=\s?\t?(=\?([^?]+)\?([^?]+)\?)/g;
	text = text.replace(encoded_multipart_regex_pattern, "")
	
	var encoded_part_regex_pattern = /(=\?([^?]+)\?([^?]+)\?)([^?]+)\?=/;
	var match_result = encoded_part_regex_pattern.exec(text);
	if (match_result != null) {
		var front = middle = back = "";
		var firstIndex = match_result.index;
		var lastIndex = encoded_part_regex_pattern.lastIndex;
		encoding_prefix = match_result[1];
		
		if (firstIndex !== 0) {
			front = text.slice(0, firstIndex);
		}
		
		try {
			middle = st.Decode(match_result[0].slice(0, -2), "auto");
		}
		catch (err) {
			if (Script.config.verbose) {
				return "[Unsupported Encoding]";
			} else {
				return "";
			}
		}
		
		if (lastIndex !== text.length - 1) {
			back = text.slice(lastIndex);
		}
		
		return String(front + middle + back);
	}
	
	return text;
}

// Helper Function
// Print only if requested.
function print(text) {
	if (Script.config.verbose) {
		DOpus.Output(text);
	}
}

// Helper Function
// Display error message.
function error(text) {
	DOpus.Output(text, true);
}

// Helper Function
// Return true if 'item' is from file type group 'needle'.
function isFromGroup(item, needle) {
	if (item.groups.length) {
		for (var i = 0; i < item.groups.length; i++) {
			if (item.groups(i).display_name === needle) return true;
		}
	}
	return false;
}

// Helper Function
// Return string value path with quotes if all is true or if the path contains a space.
// Thanks goes to "spiralx".
// https://resource.dopus.com/t/scripts-js-snippet-collection-functions-and-helpers/18387
function quotePath(path, all) {
	path = String(path);
	if ((path.indexOf('"') === 0) && (path.lastIndexOf('"') === path.length - 1)) {
		return path;
	} else {
		all = typeof all === "boolean" ? all : false;
		return all || path.indexOf(' ') !== -1 ? ('"' + path + '"') : path;
	}
}
2 Likes

I search forum how to display columns for my msg files and I found this script but I have problem.
All msg files display the same wrong date : 01/01/0 0-669:0-14:0-8
And nothing for subject/from address/message-ID/Newsgroups/to address and nothing in log except " Using 'DOpus.Vars' cache.".

Can I have some help ?