Update a subscriber custom field using campaign monitor API and python - python

I've tried getting this to work every which way I can. I've poured through the campaign monitor API docs and search every possible article on the internet I could find. I didn't find one single example of code to update a subscriber record. I can get the record. I just can't update it and I am hoping someone can help me figure out what I am doing wrong. Thank you!
from createsend import *
def GetSubscribers(token, refresh_token, list_id):
auth = {
'access_token': token,
'refresh_token': refresh_token}
user_email = 'email#example.com'
subscriber = Subscriber(auth, list_id, user_email).get()
for i in subscriber.CustomFields:
for n in i.__dict__.keys():
print(i.__dict__[n])
if i.__dict__[n] == "campaign_code":
print(n, i.__dict__[n])
print(i.__dict__["Value"])
campaign_code = i.__dict__["Value"]
new_campaign_code = campaign_code + "_REPLIED"
print(new_campaign_code)
custom_fields = [
{"Key": i.__dict__[n], "Value": new_campaign_code}]
Subscriber.update(user_email, "Subscriber",
custom_fields, True, True, "Yes")
params = {"email": self.email_address}
AttributeError: 'str' object has no attribute 'email_address'

I figured it out. This is the correct structure to update a subscriber using the createsend python wrapper:
custom_fields = [{"Key": key, "Value": value}]
subscriber = Subscriber(auth, list_id, user_email).update(
new_user_email, "Subscriber", custom_fields, True, "Yes")
This one took a lot of time for me to figure out. I hope this post will help save some time for anyone trying to update subscribers in campaign monitor.

Related

CloudKit Server-to-Server auth: Keep getting 401 Authentication failed

I have been recently exploring the CloudKit and related frameworks. I got the communication with my app working, as well as with my website using CloudKitJS. Where I am struggling is the Server-to-Server communication (which I would need for exporting data from public database in csv.
I have tried Python package requests-cloudkit, which others were suggesting. I have created a Server-to-Server token, and have copied only the key between START and END line once creating the eckey.pem file. I then got this code:
from requests_cloudkit import CloudKitAuth
from restmapper import restmapper
import json
KEY_ID = '[my key ID from CK Dashboard]'
SECRET_FILE_KEY = 'eckey.pem'
AUTH = CloudKitAuth(KEY_ID, SECRET_FILE_KEY)
PARAMS = {
'query':{
'recordType': '[my record type]'
},
}
CloudKit = restmapper.RestMapper("https://api.apple-cloudkit.com/database/1/[my container]/development/")
cloudkit = CloudKit(auth=AUTH)
response = cloudkit.POST.public.records.query(json.dumps(PARAMS))
I am then getting the 401 Authentication failed response. I am stuck on this for days, so I would be grateful for any help or advice. 😊
Creating the server-to-server key is an important first step, but in order to make HTTP requests after that, you have to sign each request.
Look for the Authenticate Web Service Requests section near the bottom of this documentation page.
It's a little bit convoluted, but you have to carefully construct signed headers to include with each request you make. I'm not familiar with how to do it in Python, but here's how I do it in NodeJS which may help:
//Get the timestamp in a very specific format
let date = moment().utc().format('YYYY-MM-DD[T]HH:mm:ss[Z]')
//Construct the subpath
let endpoint = '/records/lookup'
let path = '/database/1/iCloud.*****/development/public'
let subpath = path+endpoint
//Get the key file
let privateKeyFile = fs.readFileSync('../../'+SECRET_FILE_KEY, 'utf8')
//Make a string out of your JSON query
let query = {
recordType: '[my record type]'
}
let requestBody = JSON.stringify(query)
//Hash the query
let bodyHash = crypto.createHash('sha256').update(requestBody, 'utf8').digest('base64')
//Assemble the components you just generated in a special format
//[Current date]:[Request body]:[Web service URL subpath]
let message = date+':'+bodyHash+':'+subpath
//Sign it
let signature = crypto.createSign('RSA-SHA256').update(message).sign(privateKeyFile, 'base64')
//Assemble your headers and include them in your HTTP request
let headers = {
'X-Apple-CloudKit-Request-KeyID': KEY_ID,
'X-Apple-CloudKit-Request-ISO8601Date': date,
'X-Apple-CloudKit-Request-SignatureV1': signature
}
This is a bit hairy at first, but I just put all this stuff in a function that I reuse whenever I need to make a request.
Apple's documentation has pretty much been abandoned and it's hard to find good help with CloudKit Web Services these days.

Jira API Assignee not populating with "name" anymore, instead needs "accoundId"

I'm working with Python 3.x
Previously, I had a function to create tickets that looks like this
def jira_incident(jira_subject, jira_description):
user = "username"
apikey = 'apikey'
server = 'https://serverName.atlassian.net'
options = {
'server': server,
'verify': False
}
issue_dict = {
'project': {'key': 'project_name'},
'summary': str(jira_subject),
'description': str(jira_description),
'issuetype': {'name': 'Incident'},
'assignee': {'name': my_username},
'priority': {'name': 'Low'},
'customfield_10125':
{'value': 'Application Ops'}
}
jira = JIRA(options, basic_auth=(user, apikey))
new_issue = jira.create_issue(fields=issue_dict)
return new_issue
my_username is a global variable that's used for other things as well.
Anyway, the assignee is no longer working as of about 2 days ago. I did some googling and found that it now needs the accountId instead of the name, I can get this via the web UI by leaving a comment as #'ing someone in a comment. As a temporary solution I've populated a dictionary to reference (and that works), however I'd like to make this more dynamic for future proofing the script.
'assignee': {'accountId': jira_dict[my_username]},
I can't seem to really find any documentation on looking up the accountId from the name, and I figured I'd go ahead and ask the community to see if anyone else has run into/solved this issue.
I was thinking about just writing a new function that performs this query for me, then returns the accountId.
EDIT
I did find this:
import requests
from requests.auth import HTTPBasicAuth
import json
url = "/rest/api/3/user/bulk/migration"
auth = HTTPBasicAuth("email#example.com", "<api_token>")
headers = {
"Accept": "application/json"
}
response = requests.request(
"GET",
url,
headers=headers,
auth=auth
)
print(json.dumps(json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")))
However it 404's on me, I add the server address to the beginning of the url, and replace user, with the username in question.
Okay, I found a solution, it's not an elegant solution, but it does exactly what I need it to. So here is the new function:
def jira_account_id_from_username(username):
r = requests.get('https://serverName.atlassian.net/rest/api/3/user?username=' + username, auth=("username",api_key), verify=False)
value = re.search('"accountId":"(.*?)",', str(r.text)).group(1)
return value
I strongly encourage you to not rely on the username anymore. The endpoint you are using is deprecated, see also https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-user-privacy-api-migration-guide/.
The "new" or probably better way is to use the /user/search endpoint as described here: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-user-search-get There you can define a query that is matching against certain properties of a user (displayName or emailAddress), or search for the accountId if you already have it. Therefore, if you are linking users from the cloud to some other "user directory" (or just a scripts where you have stored some usernames), replace it by using either email address or accountId so you can properly link your users.

How to find workspace name?

How do I find the workspace name from the message using Slack bot for Python? I can find username using the following:
username = message.channel._client.users[message.body['user']]['id']
But I don't know how to find the workspace name.
One approach is to call auth.test with the token of the respective team ID.
It will return the workspace name for the token / team ID in the team property.
Example output:
{
"ok": true,
"url": "https://subarachnoid.slack.com/",
"team": "Subarachnoid Workspace",
"user": "grace",
"team_id": "T12345678",
"user_id": "W12345678"
}
Example code for Python 3.6+ / slackClient 2.1:
import slack
client = slack.WebClient(token='YOUR_TOKEN')
response = client.auth_test()
print(response['team'])
Example code for Python < 3.6 / slackClient v1
from slackclient import SlackClient
response = self.sc.api_call('auth.test')
if not response['ok']:
raise RuntimeError("...")
else:
print response['team']
Actually I was experimenting with the above solution and decided to explore the dictionary that was returned with:
username = message.channel._client.users[message.body['user']]
It is possible to obtain team id much easier with Slack bot like this (the solution was in plain sight all along):
username = message.channel._client.users[message.body['user']]['team_id']
But thank you for your assistance, #Erik! :)

Facebook marketing api 'Unsupported get request. Object with ID '[number_of_ad_account_id]' does not exist

I'm new on Facebook marketing API.
1) they let me open only a sandbox account and I want to create a real one and get a real report.
2) after generating the example code and working with Python SDK and getting this error -
Unsupported get request. Object with ID '[number_of_ad_account_id]' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https://developers.facebook.com/docs/graph-api
please help me solve this issues.
thanks.
EDIT: CODE (take from Facebook sample code)
from facebookads.adobjects.adaccount import AdAccount
from facebookads.adobjects.adsinsights import AdsInsights
from facebookads.api import FacebookAdsApi
access_token = '{secret}'
ad_account_id = '{secret}'
app_secret = '{secret}'
FacebookAdsApi.init(access_token=access_token)
fields = [
'account_id',
'adgroup_id',
'campaign_group_id',
'campaign_id',
]
params = {
'time_range': {'since':'2017-07-10','until':'2017-07-10'},
'filtering': [],
'level': 'account',
'breakdowns': [],
}
print AdAccount(ad_account_id).get_insights(
fields=fields,
params=params,
)
I had the same problem. The SAMPLE_CODE.py auto-generated from Marketing API's quickstart doesn't prefix ad_account_id with act_ by default; it puts the Sandbox Account ID directly, while the Ad Insights API expects:
act_<AD_ACCOUNT_ID>/insights
Just prefix you ad_account_id with act_ and it will work.

How to unsubscribe / delete list members using Mailchimp3 in Python?

I am using mailchimp3 in Python. I managed to make batch insertion of users using this function:
client = MailChimp(USERNAME, APIKEY)
def fill_list(list_id, subscribers_data):
data = {'operations': create_subscriptions_data(list_id, subscribers_data)}
client.batches.create(data)
def create_subscriptions_data(list_id, users_data):
return [{'method': 'PUT',
'path': 'lists/{}/members/{}'.format(list_id, str(md5(user['email_address'].encode('utf-8')))),
'body': json.dumps(user)} for user in users_data]
Here is how one user dict looks like:
{"email_address": "user#somemail.com", "status": "subscribed"}
Then I wanted to use similar method to unsubscribe list of users. To achieve that I tried to use the same logic, just to change the user objects. Now, I used this user format:
{"email_address": "user#somemail.com", "status": "unsubscribed"}
It doesn't update the subscribe status. When I deleted all users manually (using the web interface) and tried the same command I successfully created users with "status": "unsubscribed". I am wondering why this approach can't change the status? I tried also using POST instead of PUT, but it didn't work. Any idea what can be the issue?
I used this reference https://devs.mailchimp.com/blog/batch-operations-and-put-in-api-v3-0/ and it mentions that this approach should work fine for updates as well.
Thank you in advance!
The only way to unsubscribe an already subscribed user will be to update with a list id and an MD5 hash of the lowercase version of the list member’s email address.
client.lists.members.update('LIST_ID', 'MD5 HASH', {"status":
"unsubscribed"})
Actually, I was using some wrong functions, so here is the fixed code. I also had some problems with the size of the batches. The maximum batch size is 500, so I did some splits of the data across several batches.
Here is a simple code how the insertion should be done:
client = MailChimp(USERNAME, APIKEY)
def _update_list(list_id: str, members_data: list):
client.lists.update_members(list_id, {'members': members_data, 'update_existing': True})
Each member in members_data has data like this:
mailchimp_user = {
'email_address': user.email,
'status': user.subscription_status,
'merge_fields': {
'FNAME': user.first_name,
'LNAME': user.last_name
},
'interests': {}
}
And here comes the most important function:
def fill_in_multiple_batches(list_id, mailchimp_members):
step_size = 400
for i in range(0, len(mailchimp_members), step_size):
batch_start_idx = i
batch_end_idx = min(i + step_size, len(mailchimp_members))
this_batch_of_members = mailchimp_members[batch_start_idx:batch_end_idx]
client.lists.update_members(list_id, {'members': members_data, 'update_existing': True})
After that, in the main of the script:
if __name__ == '__main__':
fill_in_multiple_batches('your_list_id', your_data_list)

Categories