Action

Post/Update to Micro.blog

Posted by donnydavis, Last update 6 months ago

UPDATES

6 months ago

Hopefully fixing an issue with the category selection.

show all updates...

6 months ago

Hopefully fixing an issue with the category selection.

6 months ago

  • Refactored the code to make it a little easier to maintain and add new features going forward.
  • Hopefully fixed a bug where the categories were no longer showing in some instances.
  • Fixed an issue where updating a post was broken.
  • When creating a new post it will no longer include the trailing slash for the domain name. This is to comply with recent changes to tagging that recommend not having a tag with a trailing slash. However, if there are existing tags that have the trailing slash, then everything will still work as expected.

11 months ago

  • Fixed a but with the authentication toke, where it was possible to include an empty space at the beginning or end. This would cause it to fail. Stripped out these spaces to avoid this issue.
  • Fixed an issue with cross posting. If you unchecked all options, it would cross post everywhere. Sorry about that. Now it should allow you to properly cross post how you choose.
  • New feature, Scheduled Posts. You can now schedule a post directly from Drafts. When creating the post just set the date and time that you would like the post to publish. At the moment you cannot change the scheduled date in Drafts, you will need to update that on the website for now.

Post to your Micro.blog hosted blog.

If you have multiple blogs, then you can select which one to post to. You can select if you would like to create a draft or publish directly. You can also select which, if any, categories to add.

If you would like a title for your post then add a # to the first line. Otherwise it will post without a title.

Posting options that are available:
* Setting the post status.
* Selecting cross posting targets.
* Selecting categories.

After posting, whether it’s a draft or published, you can use this action to update the post as well. To make this happen, there are tags added to the draft that contain the domain that it is posted to and the post slug. If these are removed then it will create a new post. There is also a tag added that reflects the post status, if it is a draft or published.

Steps

  • script

    // Post to Micro.blog Hosted Blog
    // Define classess and objects.
    
    // Main client that contains the app token and
    // the base request parameters.
    
    class MBClient {
    
    	// Public properties
    	
    	domains = [];
    	categories = [];
    	
    	get post() {
    		return this.#_post;
    	}
    	set post(newValue) {
    		this.#_post = newValue;
    		if (this.post.selectedDomain.length > 0) {
    			this.#getAllCategories();
    		}
    		if (this.post.isUpdating) {
    			this.#getPostProperties();
    		}
    	}
    
    	// Private properties
    
    	#appToken;
    	#_post;
    
    	constructor(appToken) {
    		this.#appToken = appToken;
    		this.post = new Post();
    		this.#getConfigValues();
    	}
    
    	// Public methods
    	
    	setSelectedDomain(selectedDomain) {
    		this.post.selectedDomain = selectedDomain;
    		this.#getAllCategories();
    	}
    		
    	sendPost() {
    		let additionalParameters= {};
    		if (this.post.isUpdating) {
    			additionalParameters = this.post.updateParameters();
    		} else {
    			additionalParameters = this.post.createParameters();
    		}
    		
    		const response = this.#performRequest(additionalParameters);
    		
    		if (response.statusCode != 200 
    			&& response.statusCode != 202) {
    			console.log("Post failed with status code: " + response.statusCode);
    			context.fail("Post failed.");
    
    		} else {
    			this.#setPostMetaData(response.headers.Location);
    		}
    	}
    	
    	// Private methods
    	
    	#baseRequestParameters() {
    		return {
    			"url": "https://micro.blog/micropub",
    			"parameters": {},
    			"headers": {
    				"Authorization": "Bearer " + this.#appToken,
    			},
    		};
    	}
    	
    	#performRequest(additionalParameters) {
    		const parameters = {
    			...this.#baseRequestParameters(), 
    			...additionalParameters
    		};
    		const req = HTTP.create();
    		return req.request(parameters);
    	}
    	
    	#parseDomain(domain) {
    		let host = domain.uid.split("://")[0];
    		return host + "://" + domain.name;
    	}
    	
    	#getConfigValues() {
    		const response = this.#performRequest(
    			{
    				"method": "GET",
    				"parameters": {
    					"q": "config"
    				}
    			}
    		);
    		
    		if (response.statusCode != 200 
    			&& response.statusCode != 202) {
    			console.log("Request for list of domains failed. Status code: " + response.statusCode);
    			context.fail();
    		
    		} else if (typeof(response.responseData.destination) == "undefined") {
    			console.log("Response for initial config info returned with no data.");
    			context.fail();
    		
    		} else {
    			this.domains = response.responseData.destination
    				.map(this.#parseDomain);
    		
    			this.post.syndicateTo = response.responseData["syndicate-to"]
    				.map(target => target.uid.charAt(0).toUpperCase() + target.uid.slice(1));
    		}
    	}
    	
    	#getAllCategories() {
    		const response = this.#performRequest(
    			{
    				"method": "GET",
    				"parameters": {
    					"q": "category",
    					"mp-destination": this.post.selectedDomain,
    				}
    			}
    		);
    		
    		console.log("Category Response: " + response.statusCode);
    	
    		if (response.statusCode != 200 
    			&& response.statusCode != 202) {
    			context.fail("Request for categories failed.");
    		
    		} else {
    			this.categories = response.responseData.categories;
    		}
    	}
    	
    	#getPostProperties() {
    		const response = this.#performRequest(
    			{
    				"method": "GET",
    				"parameters": {
    					"q": "source",
    					"mp-destination": this.post.selectedDomain,
    					"url": this.post.url,
    					"post-status": this.post.status.toLowerCase(),
    				}
    			}
    		);
    		
    		if (response.statusCode != 200 
    			&& response.statusCode != 202) {
    			console.log("Request for selected categories failed.");
    			context.fail();
    		
    		} else {
    			this.post.categories = response.responseData.properties.category;
    		
    			if (response.responseData.properties.published) {
    				this.post.publishedDate = new Date(response.responseData.properties.published);
    			}
    		}
    	}
    	
    	#setPostMetaData(url) {
    		if (this.post.isUpdating) {
    			draft.addTag(this.post.status.toLowerCase());
    			let postTag = draft.tags
    				.find(item => item.startsWith("post:"));
    			draft.removeTag(postTag);
    			
    		} else {
    			draft.addTag(this.post.status.toLowerCase());
    			draft.addTag("domain:" + this.post.selectedDomain);
    		}
    		let post = url.replace(this.post.selectedDomain + "/", "");
    		draft.addTag("post:" + post);
    	
    		draft.update();
    	}
    }
    
    // Class to contain post details
    
    class Post {
    
    	// Public properties
    	categories = [];
    	status = "Published";
    	publishedDate = new Date();
    	syndicateTo = [""];
    	title = "";
    	content = "";
    	selectedDomain = "";
    	
    	get url() {
    		if (this.selectedDomain.length > 1 && this.#slug.length > 1) {
    			return this.selectedDomain + "/" + this.#slug;
    		} else {
    			return "";
    		}
    	}
    	
    	get isUpdating() {
    		return this.url.length > 1;
    	}
    	
    	get isScheduled() {
    		const currentDate = new Date();
    		return publishedDate.getTime() > currentDate.getTime();
    	}
    
    	// Private properties
    	
    	#slug = "";
    	
    	constructor() {
    		this.#setCurrentDomain();
    		this.#setPostSlug();
    		this.#setPostStatus();
    		this.#setPostContent();
    	}
    	
    	// Private methods
    	
    	#setCurrentDomain() {
    		if (draft.tags.some(item => item.startsWith("domain:"))) {
    			this.selectedDomain = draft.tags
    				.find(item => item.startsWith("domain:"))
    				.replace("domain:", "");
    
    			const length = this.selectedDomain.length - 1;
    			if (this.selectedDomain[length] == "/") {
    				this.selectedDomain = this.selectedDomain.substring(0, length);
    			}
    		} else {
    			this.selectedDomain = "";
    		}
    	}
    	
    	#setPostSlug() {
    		if (draft.tags.some(item => item.startsWith("post:"))) {
    			this.#slug = draft.tags
    				.find(item => item.startsWith("post:"))
    				.replace("post:", "");
    		} else {
    			this.#slug = "";
    		}
    	}
    	
    	#setPostStatus() {
    		if (draft.tags.includes("draft")) {
    			this.status = "Draft";
    		} else {
    			this.status = "Published";
    		}
    	}
    	
    	#setPostContent() {
    		if (this.#hasTitle()) {
    	 		this.title = draft.processTemplate("[[safe_title]]");
    		 	this.content = draft.processTemplate("[[body]]");
    		} else {
    	 		this.content = draft.content;
    		}
    	}
    	
    	#hasTitle() {
    		const firstLine = draft.processTemplate("[[title]]");
    		return firstLine.startsWith("#");
    	}
    	
    	#formatSyndicateToValues(syndicateTo) {
    		if (syndicateTo.length) {
    			return syndicateTo.map(element => element.toLowerCase());
    		} else {
    			return [""];
    		}
    	}
    
    	// Public Properties
    	setPostProperties(prompt) {
    		if (prompt.fieldValues["postStatus"]) {
    			this.status = prompt.fieldValues["postStatus"].toString();
    		}
    		
    		const syndicateToValues = prompt.fieldValues["syndicateTo"];
    		if (Array.isArray(syndicateToValues)) {
    			this.syndicateTo = syndicateToValues;
    		}
    		
    		const categoryList = prompt.fieldValues["categories"];
    		if (categoryList) {
    			this.categories = categoryList;
    		}
    	
    		const publishDate = prompt.fieldValues["publishDate"];
    		if (publishDate) {
    			this.publishedDate = publishDate;
    		}
    	}
    	
    	createParameters() {
    		let requestParameters = {
    			"method": "POST",
    			"encoding": "form",
    			"parameters": {
    				"mp-destination": this.selectedDomain,
    			},
    			"data": {
    				"h": "entry",
    	 			"post-status": this.status.toLowerCase(),
    	 			"mp-syndicate-to": this.#formatSyndicateToValues(this.syndicateTo),
    		 		"content": this.content,
    		 		"category": this.categories,
    	 			"published": this.publishedDate,
    			}
    		};
    		
    		if (this.title.length > 0) {
    			requestParameters.data.name = this.title;
    		}
    		
    		return requestParameters;
    	}
    	
    	updateParameters() {
    		let requestParameters = {
    			"method": "POST",
    			"parameters": {
    				"mp-destination": this.selectedDomain,
    			},
    			"data": {
    				"action": "update",
    				"url": this.url,
    			 	"replace": {
    		 			"post-status": [this.status],
    		 			"content": this.content,
    		 			"category": this.categories,
    	 			}
    			}
    		};
    	 	
    	 	if (this.title.length > 0) {
    			requestParameters.data.replace.name = this.title;
    		}
    		
    		return requestParameters;
    	}
    }
    
  • script

    // Post to Micro.blog Hosted Blog
    // 
    // Prompt for app token and set up client.
    
    const credential = Credential.create("Micro.blog", "Insert Micro.blog app token generated on Micro.blog account page.");
    credential.addPasswordField("apptoken", "App Token");
    
    credential.authorize();
    
    const appToken = credential.getValue("apptoken").toString().trim();
    const micropubEndpoint = "https://micro.blog/micropub";
    
    let mbClient = new MBClient(appToken);
    
  • script

    // Post to Micro.blog Hosted Blog
    // 
    // Prompt for selected domain to post to.
    
    if (mbClient.post.isUpdating == false) {
    	promptForDomains();
    }
    
    // Prompt for a domain to post to
    function promptForDomains() {
    	let domains = mbClient.domains;
    	let prompt = Prompt.create();
    	prompt.title = "Micro.blog Post";
    	prompt.message = "Select domain to post to.";
    
    	if (domains.length > 1) {
    		let defaultDomain = domains[0];
    		prompt.addSelect("domain", "Domains", domains, [defaultDomain], false);
    		
    	} else if (domains.length == 1) {
    		mbClient.post.selectedDomain = domains[0];
    		return;
    		
    	} else {
    		console.log("No available domain to select from.");
    		context.fail();
    	}
    	
    	prompt.addButton("Next");
    	var promptResponse = prompt.show();
    	
    	if (promptResponse == false) {
    		console.log("Cancel button was pressed.");
    		context.fail();
    		
    	} else {
    		mbClient.setSelectedDomain(prompt.fieldValues["domain"].toString());
    	}
    }
    
  • script

    // Post to Micro.blog Hosted Blog
    // 
    // Prompt for posting options.
    
    promptForOptions();
    
    function promptForOptions() {	
    	let prompt = Prompt.create();
    	prompt.title = "Micro.blog Post";
    	prompt.message = "Select Posting Options";
    	
    	if (draft.hasTag("published") == false) {
    		let postStatuses = [
    			"Published",
    			"Draft"
    		]
    		prompt.addSelect("postStatus", "Post Status", postStatuses, [mbClient.post.status], false);
    		
    		if (mbClient.post.isUpdating == false) {
    			const defaultPublishDate = mbClient.post.publishedDate;
    			prompt.addDatePicker("publishDate", "Publish Date", defaultPublishDate, {
    				"minimumDate": new Date(),
    				"mode": "dateAndTime"
    			});
    		}
    	}
    	
    	if (mbClient.post.syndicateTo.length > 0 && mbClient.post.isUpdating == false) {
    		prompt.addSelect("syndicateTo", "Cross Posting", mbClient.post.syndicateTo, mbClient.post.syndicateTo, true);
    	}
    		
    	if (mbClient.categories.length > 0) {
    		prompt.addSelect("categories", "Categories", mbClient.categories, mbClient.post.categories, true);
    	}
    		
    	if (mbClient.post.isUpdating) {
    		prompt.addButton("Update");
    	} else {
    		prompt.addButton("Post");
    	}
    	const promptResponse = prompt.show();
    	
    	if (promptResponse == false) {
    		context.fail("User cancelled action");
    		
    	} else {
    		mbClient.post.setPostProperties(prompt);
    		mbClient.sendPost();
    	}
    }
    

Options

  • After Success Archive , Tags: blog
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.