update column to record if an app script is run for that row or not in google sheets - google-apps-script

I am trying to run a google app script with Whatsapp business API to send messages to my customers directly from google sheets. The below app runs fine but every time I run it, it sends the message again and again to all customers irrespective of the same msg being sent to the same customer earlier.
Is there a way, I can add a column and update it automatically to record if the message has been sent to this customer in which case skip to the next (just like in mail merge scripts).
I have the below code and a screenshot of the image here
const WHATSAPP_ACCESS_TOKEN = "**My whatsapp token**";
const WHATSAPP_TEMPLATE_NAME = "**My template name**";
const LANGUAGE_CODE = "en";
const sendMessage_ = ({
recipient_number,
customer_name,
item_name,
delivery_date,
}) => {
const apiUrl = "**My api url**";
const request = UrlFetchApp.fetch(apiUrl, {
muteHttpExceptions: true,
method: "POST",
headers: {
Authorization: `Bearer ${WHATSAPP_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
payload: JSON.stringify({
messaging_product: "whatsapp",
type: "template",
to: recipient_number,
template: {
name: WHATSAPP_TEMPLATE_NAME,
language: { code: LANGUAGE_CODE },
components: [
{
type: "body",
parameters: [
{
type: "text",
text: customer_name,
},
{
type: "text",
text: item_name,
},
{
type: "text",
text: delivery_date,
},
],
},
],
},
}),
});
const { error } = JSON.parse(request);
const status = error ? `Error: ${JSON.stringify(error)}` : `Message sent to ${recipient_number}`;
Logger.log(status);
};
const getSheetData_ = () => {
const [header, ...rows] = SpreadsheetApp.getActiveSheet().getDataRange().getDisplayValues();
const data = [];
rows.forEach((row) => {
const recipient = { };
header.forEach((title, column) => {
recipient[title] = row[column];
});
data.push(recipient);
});
return data;
};
const main = () => {
const data = getSheetData_();
data.forEach((recipient) => {
const status = sendMessage_({
recipient_number: recipient["Phone Number"].replace(/[^\d]/g, ""),
customer_name: recipient["Customer Name"],
item_name: recipient["Item Name"],
delivery_date: recipient["Delivery Date"],
});
});
};

In your situation, how about modifying your script as follows? Please modify main as follows.
From:
const data = getSheetData_();
To:
const temp = getSheetData_();
const { data, ranges } = temp.reduce((o, e, i) => {
if (e["Sent"] != "sent") {
o.data.push(e);
o.ranges.push(`E${i + 2}`);
}
return o;
}, { data: [], ranges: [] });
if (ranges.length > 0) {
SpreadsheetApp.getActiveSheet().getRangeList(ranges).setValue("sent");
}
By this modification, this script checks the column "E" of "Sent". And, the row values without "sent" in column "E" are retrieved as data. And, the value of "sent" is put into column "E" of the retrieved rows.
Reference:
reduce()

Related

React,js + Prisma better way to update sql with a button

async function change_status(object_id:number){
const response = await fetch('/api/db', {
method: 'POST',
body: JSON.parse(`{"id":${object_id}}`)
});
if (!response.ok){
throw new Error(response.statusText);
}
return await response.json();
}
I want this button to change an int in mysql
<button onClick={() => change_status(object.id)}>
change Status
</button>
/api/db.ts
export default async function handler(req: NextApiRequest,res: NextApiResponse) {
const data = JSON.parse(req.body);
const object_id = data.id;
const find_object = await prisma.objects.findFirstOrThrow({
where: {id:object_id}
});
if (find_object.status == 0){
var change = await prisma.objects.update({
where: { id: object_id },
data: { status:1 },
})
}
else {
var change = await prisma.objects.update({
where: { id: object_id },
data: { status:0 },
})
}
res.json(change);
}
I get this error SyntaxError: Unexpected token o in JSON at position 1
Is there any better way to code the button or pass object_id without a JSON
Change your fetch to
const response = await fetch('/api/db', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: object_id }),
});
And in your api simply
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { id } = req.body;
...
}
I'm just gonna drop some information about Prisma when I had these kinda problems with it.
1 - don't forget to use body: JSON.stringify(), (this might be the issue here)
2 - config your header as well.
3 - I would suggest avoiding the var keyword because of some sort of things ( like hoisting).
4 - stick with Prisma documentation.they almost covered everything

Why does this task update on ClickUp show 200 as response, but the update doesn't occur using GAS?

Their documentation example requires certain fields to be in the update body, but these values aren't always available. I've checked some instructions and this, for example, says that you only need to pass the one that you want to update, which is what I am doing. Keep getting 200, but the update doesn't actually happen.
const clickupToken = "***"
const clickupReqBody = { "Authorization": clickupToken }
const clickupUrl = "https://api.clickup.com/api/v2/"
function updateTaskStatusClickUp(updatedTaskData, taskId) {
taskid = '476ky1';
updatedTaskData = {
"status": "COMPLETE"
}
const query = {
custom_task_ids: 'false',
team_id: '',
};
const resp = UrlFetchApp.fetch(
clickupUrl + `task/` + `${taskId}?${query}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: clickupToken
},
body: JSON.stringify(updatedTaskData)
}
);
console.log(resp.getContentText());
}
Appreciate your help!
It turns out that the body needs to be payload: JSON.stringify(), as clarified by Google Apps Script legend, in the question comments above.
const clickupToken = "***"
const clickupReqBody = { "Authorization": clickupToken }
const clickupUrl = "https://api.clickup.com/api/v2/"
function updateTaskStatusClickUp(updatedTaskData, taskId) {
taskid = '476ky1';
updatedTaskData = {
"status": "COMPLETE"
}
const query = {
custom_task_ids: 'false',
team_id: '',
};
const resp = UrlFetchApp.fetch(
clickupUrl + `task/` + `${taskId}?${query}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: clickupToken
},
payload: JSON.stringify(updatedTaskData)
}
);
console.log(resp.getContentText());
}

how to get md5checksum in search (google drtive api)

I'm using drive v3 of google drive api, and I want google files name, id, mimeType and md5Checksum.
/** serarch files from google
* #param query
* #returns {Promise} with the result of the search
*
*/
async search(query, token) {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
q: `name contains '${query}' and trashed=false and mimeType != 'application/vnd.google-apps.shortcut' and mimeType != 'application/vnd.google-apps.folder'`,
fields: '*',
spaces: 'drive',
pageSize: 13,
},
};
if(query.length === 0 || !token) return [];
const result = [];
try {
const response = await axios.get(
`https://www.googleapis.com/drive/v3/files`,
config
);
const files = await response.data.files;
if (files.length) {
files.map(file => {
let googleObject = {};
googleObject.id = file.id;
googleObject.name = file.name;
googleObject.icon = file.iconLink;
googleObject.mimeType = file.mimeType;
googleObject.md5Checksum = file.md5Checksum;
result.push(googleObject);
});
} else {
result.push({
id: '',
name: 'No results',
icon: 'my-icon',
mimeType: '',
});
}
return JSON.stringify(result);
} catch (error) {
console.log('error ', error);
return null;
}
}
So i get a response from the server, something like this :
{
"id": "****",
"name": "test"
"icon": "https://drive-thirdparty.googleusercontent.com/16/type/application/vnd.google-apps.spreadsheet",
"mimeType": "application/vnd.google-apps.spreadsheet"
}
I can't figure out what I'm doing wrong, I try to replace
fields: 'files(id,name,mimeType,iconLink,md5Checksum)',
with
fields: '',* but i still have the same error (only id, name, mimetype and icon)

Web3js BatchRequest Balances from List of Tokens Fails on execute()

This is my batch.js file which contains the relevant code. I am importing some variables from other files but I've isolated the problem to this file:
const Web3 = require('web3')
const fs = require('fs')
const { convertToNumber, getTokens } = require('./utils')
const { abi, bathEndpoint, walletAddress, blockNumber } = require('./constant.js')
const web3 = new Web3(new Web3.providers.HttpProvider(bathEndpoint))
const generateContractFunctionList = ({ tokens, blockNumber }) => {
const batch = new web3.BatchRequest()
tokens.map(async ({ address: tokenAddress, symbol, decimals }) => {
// console.log('tokenAddress :>> ', tokenAddress)
if (tokenAddress != null && tokenAddress != '') {
const contract = new web3.eth.Contract(abi)
contract.options.address = tokenAddress
try {
batch.add(
contract.methods
.balanceOf(walletAddress)
.call.request({}, blockNumber)
)
} catch (error) {
console.error('Error adding request to batch for token ', tokenAddress)
}
}
})
return batch
}
const main = async () => {
//const { tokens } = await getTokens()
const tokens = JSON.parse(fs.readFileSync('./tokenArrayFormatted1.json'));
console.log('tokens retrieved :>> ', tokens.length)
// const batch = generateContractFunctionList({ tokens })
// query block number
const batch = generateContractFunctionList({ tokens, blockNumber: blockNumber })
const tokenBalances = {}
const tokensIgnored = []
let batchData
try {
batchData = await batch.execute()
} catch (error) {
console.error('Error retrieving balances for some tokens')
batchData = error
}
try {
batchData.response.forEach((res, index) => {
const { name, decimals, symbol } = tokens[index]
if (res && res._hex) {
tokenBalances[name] = `${convertToNumber(res._hex, decimals)} ${symbol}`
} else {
tokensIgnored.push(name)
}
})
} catch (error) {
console.error('Error retrieving balances for some tokens')
batchData = error
}
console.log(
'The following tokens returned an error when checking balance:',
tokensIgnored
)
console.log('----------')
console.log(
`Balance checked for ${Object.keys(tokenBalances).length} tokens:`
)
console.log(tokenBalances)
}
main()
tokenArrayFormatted1.json looks like this:
[
{
"chainId": 1,
"address": "0xf3AE5d769e153Ef72b4e3591aC004E89F48107a1",
"name": "Deeper Network",
"symbol": "DPR",
"decimals": 18
},
{
"chainId": 1,
"address": "0xf680429328caaaCabee69b7A9FdB21a71419c063",
"name": "Butterfly Protocol Governance Token",
"symbol": "BFLY",
"decimals": 18
}
]
When I run node batch.js I keep getting an error telling me that batchData.response is undefined when the code tries to do a forEach over it. I logged batch to the console and it looked like this.
Batch {
requestManager: RequestManager {
provider: HttpProvider {
withCredentials: false,
timeout: 0,
headers: undefined,
agent: undefined,
connected: false,
host: 'https://USERNAME:PASSWORD#BASEURLOFGETHNODE.com',
httpsAgent: [Agent]
},
providers: {
WebsocketProvider: [Function: WebsocketProvider],
HttpProvider: [Function: HttpProvider],
IpcProvider: [Function: IpcProvider]
},
subscriptions: Map(0) {}
},
requests: [
{
params: [Array],
callback: undefined,
method: 'eth_call',
format: [Function: bound ]
},
{
params: [Array],
callback: undefined,
method: 'eth_call',
format: [Function: bound ]
}
]
}
Where USERNAME,PASSWORD, and BASEURLOFGETHNODE refer to my actual credentials.
And then batchData which is created by the line let batchData = await batch.execute() is undefined when logged to the console. So clearly await batch.execute() is producing nothing. I am using Chainstack Geth node API's (with an archive node) and web3js as indicated above. What seems to be the problem?
let batchData = await batch.execute()
batch.execute() does not return a promise if you read the source code, in fact it sends the JSON RPC batch request and call each request callback individually, so what you need to do is turn the execute function into asynchronous one returning an array of responses, and this is how to do it :
const Jsonrpc = require('web3-core-requestmanager/src/jsonrpc');
var { errors } = require('web3-core-helpers');
function executeAsync(batch) {
return new Promise((resolve, reject) => {
var requests = batch.requests;
batch.requestManager.sendBatch(requests, (err, results) => {
results = results || [];
var response = requests.map((request, index) => {
return results[index] || {};
}).map((result, index) => {
if (result && result.error) {
return errors.ErrorResponse(result);
}
if (!Jsonrpc.isValidResponse(result)) {
return errors.InvalidResponse(result);
}
return requests[index].format ? requests[index].format(result.result) : result.result;
});
resolve(response);
});
})
}
To use this function :
(async () => {
var batch = new web3.BatchRequest();
batch.add(web3.eth.getBlock.request("latest"));
var batchResponse = await executeAsync(batch);
console.log(batchResponse);
})()

How do you mock MySQL (with node-orm2) in Node.js/Express?

I am using node.js/express with https://github.com/dresende/node-orm2 to use my MySQL database.
I am new to the node.js world and I am quite stuck so far, I don't know how to unit test (not integration test) a simple function.
Here is my server.js, loading my user model (ORM)
var express = require('express'),
orm = require('orm'),
config = require('./config/config.js'),
auth = require('./services/authentication'),
helper = require('./middlewares/helper.js'),
friends = require('./modules/friends.js');
var app = express();
app.use(orm.express('mysql://' + config.mysql.username + ':' + config.mysql.pwd + '#' + config.mysql.host + ':' + config.mysql.port + '/' + config.mysql.db, {
define: function(db, models, next) {
db.load("./models/models.js", function(err) { // loaded!
models.user = db.models.user;
});
}
}));
var middlewares = [auth.authenticate, helper.retrieveUser];
app.get('/friends', middlewares, friends.findActiveFriends);
app.listen(3000);
console.log('Listening on port 3000...');
here is the user model :
module.exports = function (db, cb) {
var User = db.define('user', {
uid : { type: 'number', rational: false, unique: true, required: true },
first_name : { type: 'text', size: 100, required: true },
last_name : { type: 'text', size: 100, required: true },
picture : { type: 'text', size: 255, required: false },
email : { type: 'text', size: 255, required: true },
creation_date : { type: 'date', time: true },
modification_date : { type: 'date', time: true }
}, {
methods: {
fullName: function () {
return this.first_name + ' ' + this.last_name;
}
},
hooks: {
beforeCreate: function (next) {
if (this.creation_date == undefined) {
this.creation_date = new Date();
}
if (this.modification_date == undefined) {
this.modification_date = new Date();
}
return next();
}
}
});
// CUSTOM FUNCTIONS
User.getByUid = function(uid, callback) {
this.find({ uid: uid }, function(err, users) {
if(err) callback(err);
if (users.length == 1) {
callback(null, users[0]);
} else {
callback('No user found with uid=' + uid);
}
});
};
User.hasMany("friends", User, {
status: { type: 'enum', values: ['pending', 'refused', 'active'] }
}, {
reverse: 'friendsrev', mergeId: 'user_id', mergeAssocId: 'friend_id'
});
return cb();
};
and here is my methods to find active friends in friends.js:
var _findActiveFriends = function(req, res) {
req.currentUser.getFriends({
status: 'active'
}, function(err, friends) {
if (err) throw err;
res.send(JSON.stringify(friends));
});
};
I would like to know how can I write a simple test (with mocha and sinon.js ?) by mocking the database connection and the request also. I need to mock the value of req.currentUser which is a user returned by the ORM in a middleware.
I just want to run unit tests and do not use a real DB or make some HTTP calls.
thanks for your help.
If you want to mock the req using sinon.js, you can do something like the following.
var sinon = require('sinon');
var friend = require('./friend');
it('some test', function(done) {
var req = {
currentUser: {
// Add all the properties/functions that you are concerned with here.
// and you can/should wrap them around sinon.spy().
// If you are not concerned with that function (i.e. you are not using it)
// then you can simply use sinon.stub() to return a stub function.
}
};
var res = {
send: sinon.spy(function(obj) {
assert.ok(obj); // yes object exists
done(); // end of test
};
};
var next = function() {};
friend.findActiveFriend(req, res, next);
});
This way you shouldn't be connecting to the model, which tests friend.js only.
Also, since I just noticed you are using orm.express, you may also want to simply mock req.models with the stubbed function you desire as above.