I have a Google spreadsheet. On my E column, I want to add recent month with the year (eg: Sept 2017) if not available on E column.Suppose there is (Aug 2017) and now when I add (Sept 2017) it adds but it replaces the existing column that is Aug 2107. My requirement is to keep both: newly added (Sept 2017) and (Aug 2017).
I want recent month on E column and shift existing column on the right that is F column. I'm doing this using C#.
static void Main(string[] args)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
var service = new SheetsService(new BaseClientService.Initializer()
{
HttpClientInitializer = GetCredential(),
ApplicationName = ApplicationName,
});
String spreadsheetId = "1lZnvQe6lTGG81hyuQvf7HjH8YpnIadFlNHjFUq_G-5Q";
String range = "E1";
SpreadsheetsResource.ValuesResource.GetRequest getRequest = service.Spreadsheets.Values.Get(spreadsheetId, range);
Data.ValueRange response = getRequest.Execute();
string recentMonth = DateTime.Now.ToString("MMM yyyy");
var recentMonthArrayValue = new List<string[]>();
recentMonthArrayValue.Add(new string[] { recentMonth });
if (response.Values[0][0].ToString() != recentMonth)
{
DateTime now = DateTime.Now;
var currentMonthStartDate = new DateTime(now.Year, now.Month, 1).ToShortDateString();
Data.BatchUpdateSpreadsheetRequest batchUpdateSpreadsheetRequest = new Data.BatchUpdateSpreadsheetRequest();
batchUpdateSpreadsheetRequest.Requests = new List<Data.Request>();
Data.Request request = new Data.Request();
batchUpdateSpreadsheetRequest.Requests.Add(request);
request.UpdateCells = new Data.UpdateCellsRequest();
var gridCoordinate = new Data.GridCoordinate();
gridCoordinate.ColumnIndex = 5;
gridCoordinate.SheetId = 0;
request.UpdateCells.Start = gridCoordinate;
request.UpdateCells.Fields = "*";
request.UpdateCells.Rows = new List<Data.RowData>();
var rowData = new Data.RowData();
request.UpdateCells.Rows.Add(rowData);
rowData.Values = new List<Data.CellData>();
var cellData = new Data.CellData();
cellData.UserEnteredValue = new Data.ExtendedValue();
cellData.UserEnteredValue.FormulaValue = "=TEXT(\"" + currentMonthStartDate + "\",\"MMM yyyy\")";
rowData.Values.Add(cellData);
SpreadsheetsResource.BatchUpdateRequest batchUpdateRequest = service.Spreadsheets.BatchUpdate(batchUpdateSpreadsheetRequest, spreadsheetId);
batchUpdateRequest.Execute();
}
}
catch (Exception e)
{
//throw;
}
}
public static UserCredential GetCredential()
{
UserCredential credential;
using (var stream =new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/sheets.googleapis.com-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
return credential;
}
}
Check the insert an empty row or column guide in Sheets API:
The following spreadsheets.batchUpdate request inserts two blank
columns at column C. A second request inserts three empty rows at row
1. The inheritBefore field, if true, tells the API to give the new columns or rows the same properties as the prior row or column;
otherwise the new columns or rows acquire the properties of those that
follow them. inheritBefore cannot be true if inserting a row at row 1
or a column at column A.
The request protocol is shown below. The Updating Spreadsheets guide
shows how to implement a batch update in different languages using the
Google API client libraries.
POST https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId:batchUpdate
{
"requests": [
{
"insertDimension": {
"range": {
"sheetId": sheetId,
"dimension": "COLUMNS",
"startIndex": 2,
"endIndex": 4
},
"inheritBefore": true
}
},
{
"insertDimension": {
"range": {
"sheetId": sheetId,
"dimension": "ROWS",
"startIndex": 0,
"endIndex": 3
},
"inheritBefore": false
}
},
],
}
Related
I would like help with a google apps script. I would like to connect a Google sheet to the Mailerlite API in order to add multiple subscribers.
I would like to post each individual row from the google sheet as a subscriber in Mailerlite.
I would like a google sheet updated with the log of API responses with each trigger.
Here is the code I am using but can't get it to work. Thanks for your help!
function postUsersData() {
function getDataFromSpreadsheet() {
var usersSheetId = 11111111 // // Put your SHEET ID here
var usersSheet = getSheetById(usersSheetId)
// Users data
var values = usersSheet.getDataRange().getValues()
return values.map(function(value) {
return makePayload(values[0], value)
})
}
function postDataToMailerlite(payload) {
// API data
var url = 'https://api.mailerlite.com/api/v2/groups/'
var token = 'xxxxxxxxxxxxxxxxx' // // Put your TOKEN here
var groupId = 11111111 // // Put your GROUP ID here
var groupUrl = url + groupId + '/subscribers/import'
var params = {
'method': 'POST',
'muteHttpExceptions': true,
'headers': {
'Authorization': 'apikey ' + token,
'Content-Type': 'application/json'
},
'payload': JSON.stringify({'subscribers': payload, 'resubscribe': false, 'autoresponders': true})
};
var response = UrlFetchApp.fetch(groupUrl, params)
return JSON.parse(response.getContentText())
}
function logAction(json) {
var logsSheetId = 11111111 // // Put your SHEET ID here
var logsSheet = getSheetById(logsSheetId)
var logsSheetValues = logsSheet.getDataRange().getValues()
var payload = {
'datetime': Date(),
'imported': json.imported.length,
'unchanged': json.unchanged.length,
'updated': json.updated.length,
'errors': json.errors.length,
'errors log': json.errors
}
Object.keys(payload).forEach(function(key) {
logsSheet.getRange(logsSheetValues.length + 1, logsSheetValues[0].indexOf(key) + 1).setValue(payload[key])
})
Logger.log('Done ' + Date())
}
function getSheetById(id) {
// Get sheet by ID
return SpreadsheetApp.getActive().getSheets().filter(
function(s) {return s.getSheetId() === id;})[0];
}
function makePayload(headers, value) {
// Make single user data JSON from data based on row number
return {
'email': value[headers.indexOf('email')],
'fields': {
'full_name': value[headers.indexOf('name')],
'job': value[headers.indexOf('job')],
'trigger': value[headers.indexOf('trigger')]
}
}
}
// Perform
const data = getDataFromSpreadsheet()
const response = postDataToMailerlite(data)
logAction(response)
}
I have tried to adapt the code in https://learn.microsoft.com/en-us/office/open-xml/how-to-insert-a-picture-into-a-word-processing-document to insert an image to a table cell in my .Net Core MVC app. However, inserting the image just corrupts the word file.
I have noticed that when I extract the corrupt docx file, the "media" folder is outside the "word" folder. However, when I manually add an image to a docx file, the "media" folder is present inside the "word" folder.
...code here...
ImagePart imagePart = wordDoc.MainDocumentPart.AddImagePart(ImagePartType.Jpeg);
using (FileStream stream = new FileStream(#$"{_env.WebRootPath}\images\mono.jpg", FileMode.Open))
{
imagePart.FeedData(stream);
}
var element = new Drawing( new DW.Inline( new DW.Extent() { Cx = 990000L, Cy = 792000L },
new DW.EffectExtent(){ LeftEdge = 0L, TopEdge = 0L, RightEdge = 0L, BottomEdge = 0L },
new DW.DocProperties(){ Id = (UInt32Value)11U, Name = "Picture 1" },
new DW.NonVisualGraphicFrameDrawingProperties( new A.GraphicFrameLocks() { NoChangeAspect = true }),
new A.Graphic( new A.GraphicData( new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties(){ Id = (UInt32Value)10U, Name = "New Bitmap Image.jpg" },
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip(new A.BlipExtensionList(new A.BlipExtension(){ Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}" })) { Embed = wordDoc.MainDocumentPart.GetIdOfPart(imagePart), CompressionState = A.BlipCompressionValues.Print, },
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties( new A.Transform2D(
new A.Offset() { X = 0L, Y = 0L },
new A.Extents() { Cx = 990000L, Cy = 792000L }),
new A.PresetGeometry(new A.AdjustValueList()){ Preset = A.ShapeTypeValues.Rectangle }))){ Uri = "https://schemas.openxmlformats.org/drawingml/2006/picture" })) { DistanceFromTop = (UInt32Value)0U, DistanceFromBottom = (UInt32Value)0U, DistanceFromLeft = (UInt32Value)0U, DistanceFromRight = (UInt32Value)0U, EditId = "50D07946" });
Table table0 = new Table();
TableProperties props0 = new TableProperties(...code here...);
table0.AppendChild<TableProperties>(props0);
var tr0 = new TableRow();
var tc0 = new TableCell();
var pp0 = new ParagraphProperties(new SpacingBetweenLines() { After = "0" }, new Justification() { Val = JustificationValues.Center });
var rn0 = new Run(element);
var pg0 = new Paragraph(pp0, rn0);
tc0.Append(new TableCellProperties(new TableCellWidth { Type = TableWidthUnitValues.Dxa, Width = "1792" })), new HorizontalMerge { Val = MergedCellValues.Restart });
tc0.Append(pg0);
tr0.Append(tc0);
table0.Append(tr0);
body.Append(table0);
doc.Append(body);
...code here...
I have figured it out. The MSDN docs are to blame.
This line in the MSDN docs:
) { Uri = "https://schemas.openxmlformats.org/drawingml/2006/picture" })
is invalid, because of the https that should be http.
Changing that makes the document open without issue.
I am trying to use refresh token when the access token expires. A similar so question is answered here. And a sample code to renew token by an action
And i end up with the following code in the startup.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var logger = loggerFactory.CreateLogger(this.GetType());
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
var httpContextAuthentication = x.HttpContext.Authentication;//Donot use the HttpContext.Authentication to retrieve anything, this cause recursive call to this event
var oldAccessToken = await httpContextAuthentication.GetTokenAsync("access_token");
var oldRefreshToken = await httpContextAuthentication.GetTokenAsync("refresh_token");
logger.LogInformation($"Refresh token :{oldRefreshToken}, old access token:{oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
logger.LogInformation("Refresh token requested. " + tokenResult.ErrorDescription);
if (!tokenResult.IsError)
{
var oldIdToken = await httpContextAuthentication.GetTokenAsync("id_token");
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
var info = await httpContextAuthentication.GetAuthenticateInfoAsync("Cookies");
info.Properties.StoreTokens(tokens);
await httpContextAuthentication.SignInAsync("Cookies", info.Principal, info.Properties);
}
x.ShouldRenew = true;
}
else
{
logger.LogInformation("Not expired");
}
}
}
});
The client setup is as follows
AllowAccessTokensViaBrowser = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
AbsoluteRefreshTokenLifetime = 86400,
AccessTokenLifetime = 10,
AllowOfflineAccess = true,
AccessTokenType = AccessTokenType.Reference
After successfully login, i am getting a 401 for every other request. And the log says
[Identity Server]2017-07-04 10:15:58.819 +01:00 [Debug]
"TjpIkvHQi../cfivu6Nql5ADJJlZRuoJV1QI=" found in database: True
[Identity Server]2017-07-04 10:15:58.820 +01:00 [Debug]
"reference_token" grant with value:
"..9e64c1235c6675fcef617914911846fecd72f7b372" found in store, but has
expired.
[Identity Server]2017-07-04 10:15:58.821 +01:00 [Error] Invalid
reference token. "{ \"ValidateLifetime\": true,
\"AccessTokenType\": \"Reference\", \"TokenHandle\":
\"..9e64c1235c6675fcef617914911846fecd72f7b372\" }"
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Token is
invalid.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Creating
introspection response for inactive token.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Information] Success
token introspection. Token status: "inactive", for API name: "api1"
Any help would by highly appreciated
UPDATE:
Basically, when the token expires i get a System.StackOverflowException on the following line
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
UPDATE 2:
Do not use HttpContext.Authentication to retrieve anything. Check my answer below to find the working implementaion
I was working on this for last two days and could not make this work. Funnily, after posting the question here, within 2 hours I make it working :)
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
WriteMessage($"{timeRemaining} and elapsed at {timeElapsed}");
if (timeElapsed > timeRemaining)
{
var oldAccessToken = x.Properties.Items[".Token.access_token"];
var oldRefreshToken = x.Properties.Items[".Token.refresh_token"];
WriteMessage($"Refresh token :{oldRefreshToken}, old access token {oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
if (!tokenResult.IsError)
{
var oldIdToken = x.Properties.Items[".Token.id_token"];//tokenResult.IdentityToken
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
x.Properties.StoreTokens(tokens);
WriteMessage($"oldAccessToken: {oldAccessToken}{Environment.NewLine} and new access token {newAccessToken}");
}
x.ShouldRenew = true;
}
}
}
Basically httpContextAuthentication.GetTokenAsync make this recursive, for that reason StackOverflowException occured.
Please let me know if this implementation has any issue
Which app_id should be used for importing into a contact column? Also, what should the mappings parameter look like?
podio.ImporterService.ImportAppItems(fileId, appId, new List<ImportMappingField> {
new ImportMappingField { FieldId = primaryFieldId, Unique = false, Value = new { column_id = "0" }},
new ImportMappingField { FieldId = contactfieldId, Unique = false, Value = new { column_id = "1", app_id = ???, mappings = new []{ ??? }}}
})
Edit:
I figured it out. Below is an example that works for me.
podio.ImporterService.ImportAppItems(373063497, 18803129, new List<ImportMappingField> {
new ImportMappingField {
FieldId = 148580608,
Unique = false,
Value = new { column_id = "0" }
},
new ImportMappingField {
FieldId = 148580614,
Unique = false,
Value = new {
mappings = new []{
new {
field_key = "mail",
unique = "true",
column_id = "4"
}
}
}
}
});
See the API documentation [1]
[1] https://developers.podio.com/doc/contacts
#Html.DropDownList("CType",new List<SelectListItem> {
new SelectListItem{Text = "All",Value = "0"},
new SelectListItem{Text = "Outgoing",Value ="Outgoing"},
new SelectListItem{Text = "Incoming", Value ="Incoming"},
}, new { #class = "form-control input-md"})
If I'm selecting the second value, I want to persist that selected value of dropdown across multiple requests