Request: automate adding and removing tags to multiple tasks [Solved!]


#1

Hello all. I am new to automation/scripting. I would like to learn how to create a script/Key Maistro/Alfred workflow to accomplish couple of simple tasks. What is the best way to do this? I started learning Apple Scripting and I think that is a little overkill to accomplish what I am trying to do.

So here is what I would like to do:

  1. Add a specific tag to a task or multiple selected tasks. (I want to implement a tag for Today, another for Tomorrow etc.)
  2. I want to be able to remove a specific tag or all tags from one or multiple tasks.

Can you please advice the best way to achieve this?

Thank you…


#2

Did you know you could change multiple things at once?

I don’t know how to script add or taking away a tag. This conversation provides some clues: OmniFocus 3 - Script to remove “Today” tag and mark complete.

Request: Can anyone share the following AppleScript snippets:

  1. Add a tag to all selected items
  2. Remove a tage to all selected items.
  3. Add today due date to all selected items.

IF, someone is willing to do that, then you set up Keyboard Maestro in the following way:


#3

Thank you so much for the response. I knew about the multiple tag editing. I would like to automate with a shortcut if possible. Thanks again and will keep an eye on the responses.


#4
// unlocked2412
// Set tags of selected tasks

(() => {
	'use strict';

	// main :: IO ()
	const main = () => {
		// USER DATA
		const listTags = ['Today', 'Errands', 'Home']

		const appOF = Application('OmniFocus')
		const lrSeln = ofSelectionLR()
		const ofTags = findTags(listTags)

		return isLeft(lrSeln) ? (
			lrSeln.Left
		) : lrSeln.Right.forEach(
			x => appOF.add(ofTags, {
				to: x.tags
			})
		)
	};

	// GENERIC -----------------------------------------------------------------
	// https://github.com/RobTrew/prelude-jxa
	// JS - Apps

	// findTags :: [String] -> [OF Tag]
	const findTags = xs => {
		const doc = Application('OmniFocus')
			.defaultDocument
		return rights(xs.map(x => {
			const tags = doc.flattenedTags.whose({
				name: x
			})
			return tags.length === 0 ? (
				Left('No Tags with name')
			) : Right(tags()[0])
		}));
	}

	// ofSelectionLR :: () -> Either [OF Task]
	const ofSelectionLR = () => {
		const
			appOF = Application('OmniFocus'),
			ds = appOF.documents;

		return bindLR(
			bindLR(

				// Documents ?
				ds.length === 0 ? (
					Left('No documents open')
				) : Right(ds[0].documentWindows),

				// Windows ?
				ws => ws.length === 0 ? (
					Left('No windows open')
				) : Right(ws[0].content.selectedTrees)
			),

			// Selection ?
			seln => seln.length === 0 ? (
				Left('No selection')
			) : Right(seln.value())
		)
	}

	// JS - Prelude

	// Left :: a -> Either a b
	const Left = x => ({
		type: 'Either',
		Left: x
	});

	// Right :: b -> Either a b
	const Right = x => ({
		type: 'Either',
		Right: x
	});

	// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
	const bindLR = (m, mf) =>
		m.Right !== undefined ? (
			mf(m.Right)
		) : m;

	// concatMap :: (a -> [b]) -> [a] -> [b]
	const concatMap = (f, xs) => [].concat.apply([], xs.map(f));

	// isLeft :: Either a b -> Bool
	const isLeft = lr =>
		lr.type === 'Either' && lr.Left !== undefined;

	// rights :: [Either a b] -> [b]
	const rights = xs =>
		concatMap(
			x => x.type === 'Either' && x.Right !== undefined ? (
				[x.Right]
			) : [], xs
		);

	// JXA MAIN ----------------------------------------------------------------
	return main();
})();
// unlocked2412
// Remove tags from selected tasks

(() => {
	'use strict';

	// main :: IO ()
	const main = () => {
		// USER DATA
		const listTags = ['Home']

		const appOF = Application('OmniFocus')
		const lrSeln = ofSelectionLR()
		const ofTags = findTags(listTags)

		return isLeft(lrSeln) ? (
			lrSeln.Left
		) : lrSeln.Right.forEach(
			x => appOF.remove(ofTags, {
				from: x.tags
			})
		)
	};

	// GENERIC -----------------------------------------------------------------
	// https://github.com/RobTrew/prelude-jxa
	// JS - Apps

	// findTags :: [String] -> [OF Tag]
	const findTags = xs => {
		const doc = Application('OmniFocus')
			.defaultDocument
		return rights(xs.map(x => {
			const tags = doc.flattenedTags.whose({
				name: x
			})
			return tags.length === 0 ? (
				Left('No Tags with name')
			) : Right(tags()[0])
		}));
	}

	// ofSelectionLR :: () -> Either [OF Task]
	const ofSelectionLR = () => {
		const
			appOF = Application('OmniFocus'),
			ds = appOF.documents;

		return bindLR(
			bindLR(

				// Documents ?
				ds.length === 0 ? (
					Left('No documents open')
				) : Right(ds[0].documentWindows),

				// Windows ?
				ws => ws.length === 0 ? (
					Left('No windows open')
				) : Right(ws[0].content.selectedTrees)
			),

			// Selection ?
			seln => seln.length === 0 ? (
				Left('No selection')
			) : Right(seln.value())
		)
	}

	// JS - Prelude

	// Left :: a -> Either a b
	const Left = x => ({
		type: 'Either',
		Left: x
	});

	// Right :: b -> Either a b
	const Right = x => ({
		type: 'Either',
		Right: x
	});

	// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
	const bindLR = (m, mf) =>
		m.Right !== undefined ? (
			mf(m.Right)
		) : m;

	// concatMap :: (a -> [b]) -> [a] -> [b]
	const concatMap = (f, xs) => [].concat.apply([], xs.map(f));

	// isLeft :: Either a b -> Bool
	const isLeft = lr =>
		lr.type === 'Either' && lr.Left !== undefined;

	// rights :: [Either a b] -> [b]
	const rights = xs =>
		concatMap(
			x => x.type === 'Either' && x.Right !== undefined ? (
				[x.Right]
			) : [], xs
		);

	// JXA MAIN ----------------------------------------------------------------
	return main();
})();
// unlocked2412
// Set due date of selected tasks to today

(() => {
	'use strict';

	// main :: IO ()
	const main = () => {
		const appOF = Application('OmniFocus')
		const lrSeln = ofSelectionLR()
		const todayDate = new Date()

		return isLeft(lrSeln) ? (
			lrSeln.Left
		) : lrSeln.Right.forEach(
			x => x.dueDate = todayDate
		)
	};

	// GENERIC -----------------------------------------------------------------
	// https://github.com/RobTrew/prelude-jxa
	// JS - Apps

	// ofSelectionLR :: () -> Either [OF Task]
	const ofSelectionLR = () => {
		const
			appOF = Application('OmniFocus'),
			ds = appOF.documents;

		return bindLR(
			bindLR(

				// Documents ?
				ds.length === 0 ? (
					Left('No documents open')
				) : Right(ds[0].documentWindows),

				// Windows ?
				ws => ws.length === 0 ? (
					Left('No windows open')
				) : Right(ws[0].content.selectedTrees)
			),

			// Selection ?
			seln => seln.length === 0 ? (
				Left('No selection')
			) : Right(seln.value())
		)
	}

	// JS - Prelude

	// Left :: a -> Either a b
	const Left = x => ({
		type: 'Either',
		Left: x
	});

	// Right :: b -> Either a b
	const Right = x => ({
		type: 'Either',
		Right: x
	});

	// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
	const bindLR = (m, mf) =>
		m.Right !== undefined ? (
			mf(m.Right)
		) : m;

	// isLeft :: Either a b -> Bool
	const isLeft = lr =>
		lr.type === 'Either' && lr.Left !== undefined;

	// JXA MAIN ----------------------------------------------------------------
	return main();
})();

#5

This is amazing. Thank you so much for the response. I will be trying this soon…You guys rock…