This Google Apps Script code Search YouTube results by keywords. I want to add View Count and Subscribes Count too.
Output Data
function youTubeSearchResults() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const values = sheet.getRange("A2:A" + sheet.getLastRow()).getValues();
const modifyResults = values.flatMap(([keywords]) => {
const searchResults = YouTube.Search.list("id, snippet", { q: keywords, maxResults: 10, type: "video", order: "viewCount", videoDuration: "short", order: "date" });
const fSearchResults = searchResults.items.filter(function (sr) { return sr.id.kind === "youtube#video" });
return fSearchResults.map(function (sr) { return [keywords, sr.id.videoId, `https://www.youtube.com/watch?v=${sr.id.videoId}`, sr.snippet.title, sr.snippet.publishedAt, sr.snippet.channelTitle, sr.snippet.channelId,`https://www.youtube.com/channel/${sr.snippet.channelId}`, sr.snippet.thumbnails.high.url] });
});
sheet.getRange(2, 2, modifyResults.length, modifyResults[0].length).setValues(modifyResults);
}
When your showing script is modified, how about the following modification?
Modified script:
function youTubeSearchResults() {
// 1. Retrieve values from column "A".
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const values = sheet.getRange("A2:A" + sheet.getLastRow()).getDisplayValues().filter(([a]) => a);
// 2. Retrieve your current values.
const modifyResults = values.flatMap(([keywords]) => {
const searchResults = YouTube.Search.list("id, snippet", { q: keywords, maxResults: 10, type: "video", order: "viewCount", videoDuration: "short", order: "date" });
const fSearchResults = searchResults.items.filter(function (sr) { return sr.id.kind === "youtube#video" });
return fSearchResults.map(function (sr) { return [keywords, sr.id.videoId, `https://www.youtube.com/watch?v=${sr.id.videoId}`, sr.snippet.title, sr.snippet.publishedAt, sr.snippet.channelTitle, sr.snippet.channelId, `https://www.youtube.com/channel/${sr.snippet.channelId}`, sr.snippet.thumbnails.high.url] });
});
// 3. Retrieve viewCounts and subscriberCounts.
const { videoIds, channelIds } = modifyResults.reduce((o, r) => {
o.videoIds.push(r[1]);
o.channelIds.push(r[6]);
return o;
}, { videoIds: [], channelIds: [] });
const limit = 50;
const { viewCounts, subscriberCounts } = [...Array(Math.ceil(videoIds.length / limit))].reduce((obj, _) => {
const vIds = videoIds.splice(0, limit);
const cIds = channelIds.splice(0, limit);
const res1 = YouTube.Videos.list(["statistics"], { id: vIds, maxResults: limit }).items.map(({ statistics: { viewCount } }) => viewCount);
const obj2 = YouTube.Channels.list(["statistics"], { id: cIds, maxResults: limit }).items.reduce((o, { id, statistics: { subscriberCount } }) => (o[id] = subscriberCount, o), {});
const res2 = cIds.map(e => obj2[e] || null);
obj.viewCounts = [...obj.viewCounts, ...res1];
obj.subscriberCounts = [...obj.subscriberCounts, ...res2];
return obj;
}, { viewCounts: [], subscriberCounts: [] });
const ar = [viewCounts, subscriberCounts];
const rr = ar[0].map((_, c) => ar.map(r => r[c]));
// 4. Merge data.
const res = modifyResults.map((r, i) => [...r, ...rr[i]]);
// 5. Put values on Spreadsheet.
sheet.getRange(2, 2, res.length, res[0].length).setValues(res);
}
When this script is run, the following flow is run.
Retrieve values from column "A".
Retrieve your current values.
Retrieve "viewCounts" and "subscriberCounts".
Merge data.
Put values on Spreadsheet.
References:
Videos: list
Channels: list
reduce()
Related
I have
const url = "/aaa/222/ccc"
const label = "/aaa/bbb/ccc"
I need to show this in breadcrumbs with labels, but when click on /bbb call 222
I try to handle this
const url = "/aaa/2222/ccc"
const label = "/aaa/bbb/ccc"
breadcrumbs: any;
this.breadcrumbs = url.split('/')
.reduce((acc, cur, i) => {
const url1 = i === 0
? `${acc[i - 1].url1}/${label.split('/')[i]}`
: undefined;
const label1 = i === 0
? `${acc[i - 1].label1}/${url.split('/')[i]}`
: undefined;
const breadcrumb = {
url1,
label1
};
acc.push(breadcrumb);
return acc;
}, []);
var numbers = [175, 50, 25];
But this is output, and this is not correct
/aaa/bbb/ccc/aaa/bbb/ccc/aaa/sites/bbb/ccc/aaa/222
I need in UI aaa/bbb/ccc but in background aaa/222/ccc
Thnx
It looks like you want to build up an array of objects containing a label and url section.
The following would do that for you.
const url = "/aaa/222/ccc"
const label = "/aaa/bbb/ccc"
createObject(url,label) {
const urlArray = url.split("/").slice(1);
const labelArray = label.split("/").slice(1);
return urlArray.map((item, index) => {
return {url: item, label: labelArray[index]}
})
}
Look up the previous accumulated url/label if i > 0, not when i === 0 in your OP.
const url = "/aaa/2222/ccc";
const label = "/aaa/bbb/ccc";
const urlPaths = url.split("/");
const labelPaths = label.split("/");
const snackbar = urlPaths.reduce((acc, urlPath, i) => {
let path;
if (i === 0) {
path = {
url: "",
label: "",
};
} else {
const { url, label } = acc[i-1];
path = {
url: `${url}/${urlPath}`,
label: `${label}/${labelPaths[i]}`
};
}
acc.push(path);
return acc;
}, []).splice(1);
console.log(snackbar);
Outputs
[{
"url": "/aaa",
"label": "/aaa"
}, {
"url": "/aaa/2222",
"label": "/aaa/bbb"
}, {
"url": "/aaa/2222/ccc",
"label": "/aaa/bbb/ccc"
}]
EDIT: the .splice(1) trailing call removes the first snackbar path (which has empty strings for the URL/label).
Need to convert the array of file paths into Treeview JSON object
Array Data:
[path1/subpath1/file1.doc",
"path1/subpath1/file2.doc",
"path1/subpath2/file1.doc",
"path1/subpath2/file2.doc",
"path2/subpath1/file1.doc",
"path2/subpath1/file2.doc",
"path2/subpath2/file1.doc",
"path2/subpath2/file2.doc",
"path2/subpath2/additionalpath1/file1.doc"]
I want below object Result:
{
"title": "path1",
"childNodes" : [
{ "title":"subpath1", "childNodes":[{ "title":"file1.doc", "childNodes":[] }] },
{ "title":"subpath2", "childNodes":[{ "title":"file1.doc", "childNodes":[] }] }
]
}
I was able to convert it into an object using the below code snippet but not able to transform the way I want it
let treePath = {};
let formattedData = {};
data.forEach(path => {
let levels = path.split("/");
let file = levels.pop();
let prevLevel = treePath;
let prevProp = levels.shift();
levels.forEach(prop => {
prevLevel[prevProp] = prevLevel[prevProp] || {};
prevLevel = prevLevel[prevProp];
prevProp = prop;
});
prevLevel[prevProp] = (prevLevel[prevProp] || []).concat([file]);
});
How can i do this????
You could reduce the parts of pathes and search for same title.
const
pathes = ["path1/subpath1/file1.doc", "path1/subpath1/file2.doc", "path1/subpath2/file1.doc", "path1/subpath2/file2.doc", "path2/subpath1/file1.doc", "path2/subpath1/file2.doc", "path2/subpath2/file1.doc", "path2/subpath2/file2.doc", "path2/subpath2/additionalpath1/file1.doc"],
result = pathes.reduce((r, path) => {
path.split('/').reduce((childNodes, title) => {
let child = childNodes.find(n => n.title === title);
if (!child) childNodes.push(child = { title, childNodes: [] });
return child.childNodes;
}, r);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have 4 dates of 4 people,I want to bring the name of the person who added that date but when I select the day the array is traversed 4 times and in one of them it brings the name.. that is to say the array travels all the people but not only the one I want.
for example on day 17 when I select it 2 alerts come out with error the third with the name of the person and the fourth alert error.. the others are similar
View picture
The code is this,the function of interest is infoDay
constructor(props) {
super(props);
this.state = {
selected: "",
usuarios: [],
};
}
componentDidMount() {
firebase
.database()
.ref("DatosCli/")
.on("child_added", (data) => {
var datos = data.val();
/* alert(JSON.stringify(datos)); */
var usuariosTemp = this.state.usuarios;
datos.key = data.key;
//Alert.alert("prueba",""+datos.longitud)
usuariosTemp.push(datos);
this.setState({ usuarios: usuariosTemp });
});
}
cargarDatos = async () => {
var userTemp = new Array();
var data = await firebase.database().ref("/DatosCli").once("value");
data.forEach((child) => {
var user = child.val();
user.key = child.key;
userTemp.push(user);
});
this.setState({ usuarios: userTemp });
};
render() {
const markedDates = {};
this.state.usuarios.forEach((usuarioTemp) => {
markedDates[usuarioTemp.date] = {
selected: true,
disableTouchEvent: false,
selectedColor: "orange",
selectedTextColor: "red",
};
});
const infoDay = (day) => {
this.state.usuarios.forEach((usuarioTemp) => {
if (day.dateString == usuarioTemp.date) {
alert(usuarioTemp.nombre);
} else {
alert("fail");
}
});
};
return (
<View style={styles.container}>
<CalendarList markedDates={markedDates} onDayPress={infoDay} />
</View>
);
}
If you remove the else statement you'll only see the correct alerts.
const infoDay = (day) => {
this.state.usuarios.forEach((usuarioTemp) => {
if (day.dateString == usuarioTemp.date) {
alert(usuarioTemp.nombre);
}
});
};
If you want to travel trough a specific user, you must have to make a dict with the user names
For example:
let usuarioTemp = {
firstUser:{day: 17},
secondUser:{day: 19}
}
And your function could it be:
const infoDay = (day, userName) => {
if(usuarioTemp[userName].date === day){
alert(userName);
}
};
And finally, I suppose you are getting the data like this:
[{nombre:"Name", date:17}, {nombre:"Name2", date:19}]
You can use this function:
function groupBy(array, key) {
let arrayReduced = array.reduce(
(result, { [key]: k, ...rest }) => {
(result[k] = rest);
return result;
},
{}
);
return arrayReduced;
}
and you'll see the data like this:
{
"Name": {date: 17},
"Name2": {date: 19}
}
Test:
//Data example
let data = [{nombre:"Name", date:17}, {nombre:"Name2", date:19}]
//Function groupBy
function groupBy(array, key) {
let arrayReduced = array.reduce(
(result, { [key]: k, ...rest }) => {
(result[k] = rest);
return result;
},
{}
);
return arrayReduced;
}
//Usage
console.log(groupBy(data, "nombre"))
I hope it helps!
genderPie()
let filter = {};
async function genderPie() {
const d = await getData();
const g = await d.reduce((a, o) => (o.GEN && a.push(o.GEN), a), []);
const gender = Object.keys(g).length;
const m = await d.reduce((a, o) => (o.GEN == 1 && a.push(o.GEN), a), []);
const male = Object.keys(m).length;
const f = await d.reduce((a, o) => (o.GEN == 2 && a.push(o.GEN), a), []);
const female = Object.keys(f).length;
var data = [{
name: 'male',
y: male,
id: 1
}, {
name: 'female',
y: female,
id: 2
}];
chart = new Highcharts.Chart({
plotOptions: {
pie: {
innerSize: '80%',
dataLabels: {
connectorWidth: 0
}
}
},
series: [{
"data": data,
type: 'pie',
animation: false,
point: {
events: {
click: function(event) {
filter.GEN = '' + this.id + '';
}
}
}
}],
"chart": {
"renderTo": "gender"
},
});
}
async function getData() {
buildFilter = (filter) => {
let query = {};
for (let keys in filter) {
if (filter[keys].constructor === Array && filter[keys].length > 0) {
query[keys] = filter[keys];
}
}
return query;
}
//FILTER DATA
//Returns the filtered data
filterData = (dataset, query) => {
const filteredData = dataset.filter((item) => {
for (let key in query) {
if (item[key] === undefined || !query[key].includes(item[key])) {
return false;
}
}
return true;
});
return filteredData;
};
//FETCH JSON
const dataset = [{
"GEN": "2"
}, {
"GEN": "1"
}, {
"GEN": "1"
}, {
"GEN": "2"
},
{
"GEN": "2"
}, {
"GEN": "2"
}, {
"GEN": "2"
}, {
"GEN": "1"
}
]
//BUILD THE FILTER
const query = buildFilter(filter);
const result = filterData(dataset, query);
console.log(result)
return result
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="gender"></div>
does anyone can explain me how to handle the following?
I have two functions that filter data and than I build a chart with Hichart
Each time a user click for example a slice of a pie chart an event is fired and an object is populated.
That object allows me to filter the dataset and redraw the chart
The last thing I'm missing is about to update the filtering functions based on the object to be populated
first I'll do this
async function getData() {
buildFilter = (filter) => {
let query = {};
for (let keys in filter) {
if (filter[keys].constructor === Array && filter[keys].length > 0) {
query[keys] = filter[keys];
}
}
return query;
}
then
filterData = (data, query) => {
const filteredData = data.filter( (item) => {
for (let key in query) {
if (item[key] === undefined || !query[key].includes(item[key])) {
return false;
}
}
return true;
});
return filteredData;
};
const query = buildFilter(filter);
const result = filterData(data, query);
my object is
let filter = {}
when a user click the slice myobject become for example
let filter = {
gen: "1"
}
Take a look at this StackBlitz project.
In getData(), I simplified your filter to this one:
return data.filter(item => {
for (const property of Object.keys(filter)) {
if (item[property] !== filter[property]) {
return false;
}
}
return true;
});
and when a slice is clicked, I call genderPie() again, after updating the filter.
You might want to separate the data request from the filtering, so that the data is downloaded only once, not every time a filter is changed.
I am using mirage for creating fake data.
scenario/default.js
export default function(server) {
server.createList('product', 48);
server.loadFixtures();
}
Above I am creating 48 products and from controller I am calling
this.store.query('product', {
filter: {
limit: 10,
offset: 0
}
}).then((result) => {
console.log(result);
});
and in mirage/config.js
this.get('/products', function(db) {
let products = db.products;
return {
data: products.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
};
});
now my question is, how to load 10 products per page? I am sending in filter 10 as page size and offset means page number.
what changes should be done to config.js to load only limited products?
In your handler in mirage/config.js:
this.get('/products', function(db) {
let images = db.images;
return {
data: images.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
};
});
You are able to access the request object like so:
this.get('/products', function(db, request) {
let images = db.images;
//use request to limit images here
return {
data: images.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
};
});
Have a look at this twiddle for a full example.
Where the this twiddle has the following:
this.get('tasks',function(schema, request){
let qp = request.queryParams
let page = parseInt(qp.page)
let limit = parseInt(qp.limit)
let start = page * limit
let end = start + limit
let filtered = tasks.slice(start,end)
return {
data: filtered
}
})
You'll just adapt it for your use like this:
this.get('products',function(db, request){
let qp = request.queryParams
let offset = parseInt(qp.offset)
let limit = parseInt(qp.limit)
let start = offset * limit
let end = start + limit
let images = db.images.slice(start,end)
return {
data: images.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
}
})
An example with todos, you can adapt it to your own use case.
// Fetch all todos
this.get("/todos", (schema, request) => {
const {queryParams: { pageOffset, pageSize }} = request
const todos = schema.db.todos;
if (Number(pageSize)) {
const start = Number(pageSize) * Number(pageOffset)
const end = start + Number(pageSize)
const page = todos.slice(start, end)
return {
items: page,
nextPage: todos.length > end ? Number(pageOffset) + 1 : undefined,
}
}
return todos
});