Getting card cover image with the Trello REST API with batching - api

I'm trying to get the cover image links for all cards from a board but the boards endpoint is only providing an attachment id for the cover image. That might be fine but I also don't see how to use that id for getting the full image link. This is my request:
${TRELLO_API_ROOT}/boards/${boardId}?key=${TRELLO_KEY}&token=${TRELLO_TOKEN}&cards=all&card_customFieldItems=true&attachments=true&attachment_fields=all
And this returns all of the cards and cover image details for each card but the information does not seem useful for getting the full image path:
cards: [{
cover: {
brightness: "light",
color: null,
idAttachment: "5eee7680d7b0295f6c52fc22",
idUploadedBackground: null,
size: "normal
}
}]
I have considered that I might need to make a request to the cards endpoint for each individual card using the batch process but that process is limited to 10 requests. A board could easily have more than 10 cards so this doesn't seem like a good solution.
Is it true that you need to send a request to each individual card in order to get the cover images?
EDIT: The only way that I see to do this is to make separate requests for each card against the attachments endpoint. This has the potential to be a lot of requests though:
const requests = data.cards.filter((item) => {
return item.cover.idAttachment;
}).map((card) => {
return fetch(`${TRELLO_API_ROOT}/cards/${card.id}/attachments/${card.cover.idAttachment}?key=${TRELLO_KEY}&token=${TRELLO_TOKEN}`)
})
Promise.all(requests)
.then(responses => {
return responses;
})
.then(responses => Promise.all(responses.map(res => res.json())))
.then(attachments => {
return attachments;
});

Answering my own question because I don't believe there is any other solution than to send an individual request for each card. But we can at least improve on it by using batch.
And one clarification; you only need to request the cover image attachment url if the card has been assigned an uploaded image as the cover. If the card cover is one of the available Unplashed images that Trello is offering now, you'll get that images url in the regular boards endpoint request as a sharedSourceUrl like this:
{
brightness: "light",
color: null,
edgeColor: "#776e5d",
idAttachment: null,
idUploadedBackground: "5efe2d6c4292a74f4c4051e0",
scaled: (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}],
sharedSourceUrl: "https://images.unsplash.com/photo-1593435220813-8e60f12e947a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjcwNjZ9&w=2560&h=2048&q=90",
size: "normal"
}
I don't know if it's an oversite that they don't do the same thing automatically for uploaded image urls but there doesn't appear to be any other way to get the card image if it's an upload.
So using the batch process, you'll need to make sure to only send 10 urls at a time. This is simple enough with a standard loop to build out the arrays. Then we can queue up the batch requests into a Promise.all and handle the response by assigning that cover url to the corresponding card. We can reduce are requests significantly this way but it would still have been nice to have the cover url available during the first boards fetch.
fetch(`${TRELLO_API_ROOT}/boards/${boardId}?cards=visible&card_customFieldItems=true&key=${TRELLO_KEY}&token=${TRELLO_TOKEN}&attachments=true&attachment_fields=all`)
.then(response => response.json())
.then((data) => {
const cardsWithCovers = data.cards.filter((item, i) => {
return item.cover.idAttachment;
});
// Creating our batched request array for fetching cover urls.
const batchedUrls = [];
let batch = [];
// Creating batches.
for (let i = 0; i < cardsWithCovers.length; i++) {
// Important here not to append the url root, api key, or token values. That's how the batch endpoint wants it.
const url = `/cards/${cardsWithCovers[i].id}/attachments/${cardsWithCovers[i].cover.idAttachment}`;
batch.push(url);
// If we have our max 10 request urls, or it's the last item, push the request url group and reset batch array.
if (batch.length === 10 || i === cardsWithCovers.length - 1) {
batchedUrls.push(batch);
batch = [];
}
}
const requests = batchedUrls.map((urls) => {
return fetch(`${TRELLO_API_ROOT}/batch?key=${TRELLO_KEY}&token=${TRELLO_TOKEN}&urls=${urls}`)
});
Promise.all(requests)
.then(responses => Promise.all(responses.map(res => res.json())))
.then(batchedData => {
// Combine the batched array responses into a single array.
const mergedCardsWithCovers = [].concat.apply([], batchedData);
data.cards.forEach((card) => {
// Finding the card cover image url and adding it to the card.
const cover = mergedCardsWithCovers.find((cardWithCover) => {
return card.cover.idAttachment === cardWithCover[200].id;
});
if (cover) card.cover.url = cover[200].url;
});
})
})

Related

How do I control the rendering of video endpoint in a video conference?

How do I control the rendering of video endpoint in a video conference? I would like to render it in my own way, change the video size and more. Also, I cannot understand, how the IDs are named for the video elements. Are they the same with the endpoint IDs?
To process the video elements yourself, you need to:
first subscribe to remote media added/removed events:
https://voximplant.com/docs/references/websdk/voximplant/endpointevents#remotemediaadded
https://voximplant.com/docs/references/websdk/voximplant/endpointevents#remotemediaremoved
then process the render event:
https://voximplant.com/docs/references/websdk/voximplant/mediarenderer#render
The video elements IDs are not the same as endpoint IDs. The elements IDs are the same name as video track ID. There is no internal way in Voximplant to get the element ID, but you still can get it. Here is a custom function that will allow you to get the IDs:
const getMediaElementIds = (call) => {
return call.getEndpoints().map((endpoint) => {
return endpoint.mediaRenderers.reduce(
(acc, mr) => {
const key = mr.kind === 'audio' ? 'audioElementId' : 'videoElementId';
const id = mr.stream.getTracks()[0]?.id;
return { ...acc, [key]: id };
},
{ endpointId: endpoint.id }
);
});
};
// returns { endpointId: string; videoElementId?: string; audioElementId?: string }[]
And by the way, there are some nuances for Vue.js rendering:
don't place the sdk objects (like client, endpoints, calls etc) into immutable stores like Vuex, Rx.js and so on.
render the MediaRenderer into a container with v-once, so Vue.js does not rerender this part

How to get over the limit of OpenSea Api?

I am trying to use OpenSea API and I noticed that I need to set a limit before retrieving assets
https://docs.opensea.io/reference/getting-assets
I figured I can use the offset to navigate through all the items, even though that's tedious. But the problem is offset itself has a limit, so are assets beyond the max offset inaccessible ?
I read that you that the API is "rate-limited" without an API key, so I assume that related to the number of requests you can make in a certain time period, am I correct about that? Or does it lift the limit of returned assets ? The documentation isn't clear about that https://docs.opensea.io/reference/api-overview
What can I do to navigate through all the assets ?
May be late answering this one, but I had a similar problem. You can only access a limited number (50) assets if using the API.
Using the API referenced on the page you linked to, you could do a for loop to grab assets of a collection in a range. For example, using Python:
import requests
def get_asset(collection_address:str, asset_id:str) ->str:
url = "https://api.opensea.io/api/v1/assets?token_ids="+asset_id+"&asset_contract_address="+collection_address+"&order_direction=desc&offset=0&limit=20"
response = requests.request("GET", url)
asset_details = response.text
return asset_details
#using the Dogepound collection with address 0x73883743dd9894bd2d43e975465b50df8d3af3b2
collection_address = '0x73883743dd9894bd2d43e975465b50df8d3af3b2'
asset_ids = [i for i in range(10)]
assets = [get_asset(collection_address, str(i)) for i in asset_ids]
print(assets)
For me, I actually used Typescript because that's what opensea use for their SDK (https://github.com/ProjectOpenSea/opensea-js). It's a bit more versatile and allows you to automate making offers, purchases and sales on assets. Anyway here's how you can get all of those assets in Typescript (you may need a few more dependencies than those referenced below):
import * as Web3 from 'web3'
import { OpenSeaPort, Network } from 'opensea-js'
// This example provider won't let you make transactions, only read-only calls:
const provider = new Web3.providers.HttpProvider('https://mainnet.infura.io')
const seaport = new OpenSeaPort(provider, {
networkName: Network.Main
})
async function getAssets(seaport: OpenSeaPort, collectionAddress: string, tokenIDRange:number) {
let assets:Array<any> = []
for (let i=0; i<tokenIDRange; i++) {
try {
let results = await client.api.getAsset({'collectionAddress':collectionAddress, 'tokenId': i,})
assets = [...assets, results ]
} catch (err) {
console.log(err)
}
}
return Promise.all(assets)
}
(async () => {
const seaport = connectToOpenSea();
const assets = await getAssets(seaport, collectionAddress, 10);
//Do something with assets
})();
The final thing to be aware of is that their API is rate limited, like you said. So you can only make a certain number of calls to their API within a time frame before you get a pesky 429 error. So either find a way of bypassing rate limits or put a timer on your requests.

API multiple requests with axios / Vue.js, advice on doing things the smarter way

First of: I'm a beginner at Vue.js/APIs so I hope my question is not too stupid (I may not be seeing the obvious) :)
So,
Using Vue.js I'm connecting to this API and want to track the history of each crypto-currencies (no issues with getting any data from the API).
Currencies information are accessible using a URL :
https://api.coinranking.com/v2/coins
And history is accessible using another :
https://api.coinranking.com/v2/coin/ID_OF_THE_COIN/history
As you can see the second url needs the id of the specific currency which is available in the first one.
I would like to find a way to make only 1 get request for all currencies and their history rather than having to make as many requests as available currencies there are (about 50 on this API), I've tried several things but none has worked yet (for instance using the coin url and storing ids of the currencies in a table then using the history url and modifying it with the ids of the table but hit a wall) .
Here's the axios get request I have for the moment for a single currency:
const proxyurl = "https://cors-anywhere.herokuapp.com/"
const coins_url = "https://api.coinranking.com/v2/coins"
const history_url = "https://api.coinranking.com/v2/coin/Qwsogvtv82FCd/history"
//COINS DATA
axios
.get(proxyurl + coins_url, {
reqHeaders
})
.then((reponseCoins) => {
// console.log(reponseCoins.data)
this.crypto = reponseCoins.data.data.coins;
})
.catch((error) => {
console.error(error)
})
//GET ALL COINS UUIDs
axios
.get(proxyurl + coins_url, {
reqHeaders
})
.then((reponseUuid) => {
this.cryptoUuidList = reponseUuid.data.data.coins;
//access to each crypto uuid:
this.cryptoUuidList.forEach(coinUuid => {
console.log("id is: " + coinUuid.uuid)
//adding uuids to table:
this.coinsUuids.push(coinUuid.uuid);
});
})
.catch((error) => {
console.error(error)
})
// COIN HISTORY/EVOLUTION COMPARISON
axios
.get(proxyurl + history_url, {
reqHeaders
})
.then((reponseHistory) => {
//get data from last element
const history = reponseHistory.data.data.history
this.lastItem = history[history.length-1]
// console.log(this.lastItem)
this.lastEvol = this.lastItem.price
// console.log(this.lastEvol)
//get data from previous element:
this.previousItem = history[history.length-2]
this.previousEvol = this.previousItem.price
})
.catch((error) => {
console.error(error)
})
I probably forgot to give some info so let me know and will gladly share if I can
cheers,
I took a look at the API, they do not seem to give a way for you to get everything you need in one request so you will have to get each coin history separately.
However, I do se a sparkline key in the returned data, with what seems to be a few of the latest prices.
I do not know your projects's specifics but maybe you could use that for your initial screen (for example a coins list), and only fetch the full history from the API when someone clicks to see the details of a coin.

How to delete all products matching a collection - Shopify

I have this retarded amount of product in a collection on Shopify (over 50k products) and I would need to delete them all, is there a way I can automate that? All I can find on the internet is to use the "bulk edit tool" which is the most useless thing I've ever seen as it can only grab 50 products at a time.
I've tried automating a script to update the rows with the CSV export file, but it takes over 6 hours for 20K products to import. Plus, since there are hashtags in the title and handle, it apparently doesn't overwrite the products for some reason. So I just can't use the archive anymore...
Has anyone ran into this issue and found a solution?
Thank you!
When it comes to this kinds of tasks I usually write myself a quick dev console script that will do the job for me instead of relying on an app.
Here is a script that you can use in the dev console of your shopify admin page (just copy /paste):
let productsArray = [];
// Recursive function that will grab all products from a collection
const requestCollection = (collectionId, url = `https://${window.location.host}/admin/api/2020-10/collections/${collectionId}/products.json?limit=250`) => {
fetch(url).then(async res => {
const link = res.headers.get('link');
const data = await res.json();
productsArray = [...productsArray, ...data.products];
if(link && link.match(/<([^\s]+)>;\srel="next"/)){
const nextLink = link.match(/<([^\s]+)>;\srel="next"/)[1];
requestCollection(collectionId, nextLink)
} else {
initDelete(productsArray)
}
})
}
// Get CSRF token or the request will require password
const getCSRFToken = () => fetch('/admin/settings/files',{
headers: {
"x-requested-with": "XMLHttpRequest",
"x-shopify-web": 1,
"x-xhr-referer": `https://${window.location.host}/admin/settings/files`
}
}).then(res => res.text()).then(res => {
const parser = new DOMParser();
const doc = parser.parseFromString(res, 'text/html');
return doc.querySelector('meta[name="csrf-token"]').getAttribute('content')
})
// The function that will start the deleting process
const initDelete = async (products) => {
const csrfToken = await getCSRFToken();
products.forEach(item => {
fetch(`https://${window.location.host}/admin/api/2020-10/products/${item.id}.json`, {
method: "delete",
credentials: 'include',
headers: {
"x-csrf-token": csrfToken,
"x-requested-with": "XMLHttpRequest",
"x-shopify-web": 1,
"x-xhr-referer": `https://${window.location.host}/admin/settings/files`
}
})
})
}
And you start it by using requestCollection(ADD_YOUR_COLLECTION_ID_HERE).
To clarify the script, there are 3 main functions:
requestCollection - this handles the product grabbing from the collection. It's a recursive function since we can't grab more than 250 products at the same time.
getCSRFToken - this grabs the CSRF token since most of the post/update/delete request requires it or they will fail (I grab it from the files page)
initDelete - this function start the delete process where we stack all the request one of the other without waiting, you may want to await each request, but even if you crash your browser I think it will be still faster to repeat the process rather then wait for each request to finish.
If you plan to use this script please TEST IT BEFORE USING IT. Create a collection with a few products and run against that, in case there are issues. I've tested it on my side and it's working but it's a code I wrote in 10 minutes after midnight, there can be issues there.
Have in mind that this script will delete ALL products in the collection you specify in the requestCollection(1231254125) method.
PS: All of this can be done using a Private App as well with the products scope set to read/write, using a back-end language of your choice. The main difference will be that you won't need the CSRF token and most of the headers that I set above. But I like quick solutions that doesn't require you to pull out the big guns.

A better way to handle async saving to backend server and cloud storage from React Native app

In my React Native 0.63.2 app, after user uploads images of artwork, the app will do 2 things:
1. save artwork record and image records on backend server
2. save the images into cloud storage
Those 2 things are related and have to be done successfully all together. Here is the code:
const clickSave = async () => {
console.log("save art work");
try {
//save artwork to backend server
let art_obj = {
_device_id,
name,
description,
tag: (tagSelected.map((it) => it.name)),
note:'',
};
let img_array=[], oneImg;
imgs.forEach(ele => {
oneImg = {
fileName:"f"+helper.genRandomstring(8)+"_"+ele.fileName,
path: ele.path,
width: ele.width,
height: ele.height,
size_kb:Math.ceil(ele.size/1024),
image_data: ele.image_data,
};
img_array.push(oneImg);
});
art_obj.img_array = [...img_array];
art_obj = JSON.stringify(art_obj);
//assemble images
let url = `${GLOBAL.BASE_URL}/api/artworks/new`;
await helper.getAPI(url, _result, "POST", art_obj); //<<==#1. send artwork and image record to backend server
//save image to cloud storage
var storageAccessInfo = await helper.getStorageAccessInfo(stateVal.storageAccessInfo);
if (storageAccessInfo && storageAccessInfo !== "upToDate")
//update the context value
stateVal.updateStorageAccessInfo(storageAccessInfo);
//
let bucket_name = "oss-hz-1"; //<<<
const configuration = {
maxRetryCount: 3,
timeoutIntervalForRequest: 30,
timeoutIntervalForResource: 24 * 60 * 60
};
const STSConfig = {
AccessKeyId:accessInfo.accessKeyId,
SecretKeyId:accessInfo.accessKeySecret,
SecurityToken:accessInfo.securityToken
}
const endPoint = 'oss-cn-hangzhou.aliyuncs.com'; //<<<
const last_5_cell_number = _myself.cell.substring(myself.cell.length - 5);
let filePath, objkey;
img_array.forEach(item => {
console.log("init sts");
AliyunOSS.initWithSecurityToken(STSConfig.SecurityToken,STSConfig.AccessKeyId,STSConfig.SecretKeyId,endPoint,configuration)
//console.log("before upload", AliyunOSS);
objkey = `${last_5_cell_number}/${item.fileName}`; //virtual subdir and file name
filePath = item.path;
AliyunOSS.asyncUpload(bucket_name, objkey, filePath).then( (res) => { //<<==#2 send images to cloud storage with callback. But no action required after success.
console.log("Success : ", res) //<<==not really necessary to have console output
}).catch((error)=>{
console.log(error)
})
})
} catch(err) {
console.log(err);
return false;
};
};
The concern with the code above is that those 2 async calls may take long time to finish while user may be waiting for too long. After clicking saving button, user may just want to move to next page on user interface and leaves those everything behind. Is there a way to do so? is removing await (#1) and callback (#2) able to do that?
if you want to do both tasks in the background, then you can't use await. I see that you are using await on sending the images to the backend, so remove that and use .then().catch(); you don't need to remove the callback on #2.
If you need to make sure #1 finishes before doing #2, then you will need to move the code for #2 intp #1's promise resolving code (inside the .then()).
Now, for catching error. You will need some sort of error handling that alerts the user that an error had occurred and the user should trigger another upload. One thing you can do is a red banner. I'm sure there are packages out there that can do that for you.