Skip to content

Instantly share code, notes, and snippets.

@mfrancois3k
Forked from kentcdodds/index.js
Created April 19, 2023 04:56
Show Gist options
  • Save mfrancois3k/3f6efd78f9d6c733867d1ee242f8d02e to your computer and use it in GitHub Desktop.
Save mfrancois3k/3f6efd78f9d6c733867d1ee242f8d02e to your computer and use it in GitHub Desktop.
ConvertKit Sync ConvertKit with TestingJavaScript.com
const fs = require('fs/promises')
const fetch = require('make-fetch-happen').defaults({
cacheManager: './node_modules/.cache/make-fetch-happen',
})
const csv = require('csvtojson')
const {CONVERT_KIT_API_KEY, CONVERT_KIT_API_SECRET} = process.env
const levels = ['standard-testing', 'basic-testing', 'pro-testing']
const appendToFinished = ({
email,
level,
id = 'No Subscriber',
needs = [],
remove = [],
}) => {
return fs.appendFile(
'./finished.csv',
[email, level, id, needs.join(';'), remove.join(';')].join(',') + '\n',
)
}
async function go() {
const data = await csv().fromFile('./data.csv')
const finishedEmails = (await csv().fromFile('./finished.csv')).map(
f => f.email,
)
console.log(`Processing ${data.length} emails`)
for (const item of data) {
const {email, level} = item
if (finishedEmails.includes(email)) continue
const percent = ((data.indexOf(item) / data.length) * 100).toFixed(2)
console.log(`${percent}%`)
const subscriber = await getConvertKitSubscriber(email)
if (!subscriber) {
// console.log('no subscriber:', item)
appendToFinished(item)
continue
}
const subscriberTags = await getTags(subscriber)
const purchasedTestingId = tagIds['purchased-testing']
const needs = [
subscriberTags.includes(purchasedTestingId) ? null : purchasedTestingId,
].filter(Boolean)
const remove = []
for (const levelName of levels) {
const tagId = tagIds[levelName]
const hasTag = subscriberTags.includes(tagId)
if (!tagId) throw new Error(`No tagId for ${levelName}`)
if (level === levelName && !hasTag) needs.push(tagId)
else if (level !== levelName && hasTag) remove.push(tagId)
}
const promises = [
needs.length ? addTag(email, needs) : null,
...remove.map(r => removeTag(subscriber, r)),
].filter(Boolean)
if (promises.length) {
const result = await Promise.all(promises)
const meta = {
id: subscriber.id,
email,
level,
needs: needs.map(n => tagNames[n]),
remove: remove.map(r => tagNames[r]),
}
// console.log('done updating:', meta)
appendToFinished(meta)
} else {
const meta = {email, level, id: subscriber.id}
// console.log('no update needed:', meta)
appendToFinished(meta)
}
}
console.log('All done...')
}
async function getConvertKitSubscriber(email) {
const url = new URL('https://api.convertkit.com/v3/subscribers')
url.searchParams.set('api_secret', CONVERT_KIT_API_SECRET)
url.searchParams.set('email_address', email)
const data = await fetch(url.toString()).then(r => r.json())
const {subscribers: [subscriber] = []} = data
return subscriber?.state === 'active' ? subscriber : null
}
async function getTags(subscriber) {
return fetch(
`https://api.convertkit.com/v3/subscribers/${subscriber.id}/tags?api_key=${CONVERT_KIT_API_KEY}`,
)
.then(r => r.json())
.then(d => d.tags.map(t => String(t.id)))
}
const tagIds = {
purchased: '746921',
'purchased-testing': '746923',
'standard-testing': '746934',
'basic-testing': '746932',
'pro-testing': '746922',
}
const tagNames = {
[tagIds.purchased]: 'purchased',
[tagIds['purchased-testing']]: 'purchased-testing',
[tagIds['standard-testing']]: 'standard-testing',
[tagIds['basic-testing']]: 'basic-testing',
[tagIds['pro-testing']]: 'pro-testing',
}
async function addTag(email, tags) {
return fetch(
`https://api.convertkit.com/v3/tags/${tagIds.purchased}/subscribe`,
{
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify({
api_key: CONVERT_KIT_API_KEY,
api_secret: CONVERT_KIT_API_SECRET,
email,
tags,
}),
},
).then(r => r.json())
}
async function removeTag(subscriber, tagId) {
return fetch(
`https://api.convertkit.com/v3/subscribers/${subscriber.id}/tags/${tagId}?api_secret=${CONVERT_KIT_API_SECRET}`,
{method: 'DELETE'},
).then(r => r.json())
}
function makeGo() {
go().catch(error => {
if (error.stack?.includes?.('Unexpected token R in JSON at position 0')) {
console.log('rate limited... wait a minute then continue.')
setTimeout(() => {
makeGo()
}, 60 * 1000)
}
console.error(error.stack)
})
}
makeGo()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment