Skip to content

Instantly share code, notes, and snippets.

@bramses
Last active February 28, 2024 18:24
Show Gist options
  • Save bramses/e63c7342fd036d2d5cc7f0ba93070e99 to your computer and use it in GitHub Desktop.
Save bramses/e63c7342fd036d2d5cc7f0ba93070e99 to your computer and use it in GitHub Desktop.
Uploading a file from iOS Shortcuts to Cloudflare Workers

Create a Shortcut with two steps:

  1. select photos
  2. get contents from url
  3. set method to post
  4. set request body to form
  5. use magic variable to set file as result of last step

Screen Shot 2022-03-15 at 5 09 37 PM

const { default: axios } = require("axios");
// fs promises
const { promises } = require("fs");
const { readFile } = promises;
const FormData = require("form-data");
const convert = require("heic-convert");
require("dotenv").config();
const convertHEIC = async (inputBuffer, quality = 1.0) => {
try {
console.log('Converting HEIC to JPEG with quality = ' + quality);
const outputBuffer = await convert({
buffer: inputBuffer,
format: "JPEG", // output format
quality: quality, // the jpeg compression quality, between 0 and 1
});
return outputBuffer;
} catch (err) {
throw new Error('Error converting HEIC to JPEG: ' + err);
}
};
const uploadToCloudflare = async (filename, file, heicBuffer = null) => {
try {
let blob;
if (!file) {
blob = await readFile(filename);
} else {
blob = file;
}
let quality = 0.75
while (blob.length.toString().length >= 8 && quality >= 0.25) {
if (!heicBuffer) {
throw new Error('HEIC buffer is required to convert to JPEG');
}
console.log('File is too large to be sent to Cloudflare -- decreasing quality to ' + quality);
blob = await convertHEIC(heicBuffer, quality);
quality -= 0.25;
}
if (blob.length.toString().length > 8) {
throw new Error('File is too large to be sent to Cloudflare');
}
console.log(`Uploading file to cloudflare...${filename} :: ${blob.length}`);
const form = new FormData();
form.append("file", blob, filename);
const resp = await axios.post(
"YOUR_CF_IMAGE_URL",
form,
{
headers: {
...form.getHeaders(),
Authorization: `Bearer ${process.env.CF_IMAGE_KEY}`,
"Content-Type": "multipart/form-data",
},
}
);
return extractURL(resp.data);
} catch (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
throw new Error("Error uploading to Cloudflare " + JSON.stringify(error.response));
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
} else {
// Something happened in setting up the request that triggered an Error
console.log("Error", error.message);
throw new Error("Error uploading to Cloudflare: " + error);
}
}
};
function extractURL(input) {
try {
console.log('Extracting URL from CF response');
var variants = input.result.variants[0];
return variants;
} catch (err) {
console.error(err);
}
}
module.exports = {
uploadToCloudflare,
convertHEIC
};
// (async () => {
// const url = await uploadToCloudflare("test-img.png");
// console.log(url);
// })();
const express = require('express');
const app = express();
const port = 8080; // default port to listen
require('dotenv').config();
const { uploadToCloudflare, convertHEIC } = require('./index.js');
const fileUpload = require('express-fileupload');
app.use(fileUpload({
limits: { fileSize: 50 * 1024 * 1024 },
}));
app.post('/upload', async function(req, res) {
try {
const SHORTCUTS_API_KEY = process.env.SHORTCUTS_API_KEY;
const { apikey } = req.headers;
if (apikey !== SHORTCUTS_API_KEY) { // you'll want to secure your API call with a header; has to be lowercase from shortcuts
console.log('Invalid shortcut API key provided ' + apikey);
res.status(401).send('Unauthorized');
return;
}
const { file } = req.files;
if (!file) {
return res.status(400).send('No file uploaded');
}
if (file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png' && file.mimetype !== 'image/gif' && file.mimetype !== 'image/heic') {
return res.status(400).send('File type not supported');
}
let fileData = file.data;
if (file.mimetype === 'image/heic') {
fileData = await convertHEIC(file.data);
}
const url = await uploadToCloudflare(file.name, fileData, file.mimetype === 'image/heic' ? file.data : null);
res.send({
url,
});
} catch (err) {
res.status(500).send(err);
}
});
// start the express server
app.listen( port, () => {
console.log( `server started at http://localhost:${ port }` );
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment