Firebase Dynamic Links on Unity iOS says "Deep Link does not contain valid required params" - ios

I'm using Firebase Dynamic Links for Unity, and I've got it working well with Android. I've even got a solution for Desktop, where the fallback link takes users to a webpage where I can provide instructions to the user for how to get their link content on Desktop.
On iOS, however, I always get errors like this when trying dynamic links:
[Firebase/Analytics][I-ACS023001] Deep Link does not contain valid required params. URL params: {
"_cpb" = 1;
"_cpt" = cpit;
"_fpb" = "CIAIEIAGGgVlbi11cw==";
"_iipp" = 1;
"_iumchkactval" = 1;
"_iumenbl" = 1;
"_osl" = "https://cgs.link/zu_tiles_hime?_iipp=1";
"_plt" = 260;
"_uit" = 1064;
apn = "com.finoldigital.cardgamesim";
cid = 8062347334713659136;
ibi = "com.finoldigital.CardGameSim";
isi = 1392877362;
link = "https://www.cardgamesimulator.com/link%%3Furl%%3Dhttps://www.cardgamesimulator.com/games/zu_tiles_hime/zu_tiles_hime.json";
sd = "Play Zu Tile: Hime on CGS!";
si = "https://www.cardgamesimulator.com/games/zu_tiles_hime/Banner.png";
st = "Card Game Simulator - Zu Tiles: Hime";
}
I saw in another issue that it could be because of ?, =, and & symbols in the link, so I url-encoded those, but I am still getting the same error.
For reference, my code for iOS is effectively:
private void Start()
{
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task =>
{
var dependencyStatus = task.Result;
if (dependencyStatus != DependencyStatus.Available)
{
Debug.LogError("Could not resolve all Firebase dependencies: " + dependencyStatus);
return;
}
DynamicLinks.DynamicLinkReceived += OnDynamicLinkReceived;
});
}
I immediately log in OnDynamicLinkReceived, so this callback is clearly never happening. Does anybody know what I am doing wrong, or what I could do to get the dynamic link received callback?

For anyone who runs into the same issue:
I solved this by modifying my build script to add the values for FirebaseDynamicLinksCustomDomains and FirebaseAppDelegateProxyEnabled to Info.plist as part of my build process.
PostProcess code:
var pbxProjectPath = PBXProject.GetPBXProjectPath(buildPath);
var pbxProject = new PBXProject(); pbxProject.ReadFromFile(pbxProjectPath);
const string targetName = "Unity-iPhone"; var targetGuid = pbxProject.GetUnityMainTargetGuid();
var src = AssetDatabase.GetAssetPath(file);
var fileName = Path.GetFileName(src);
var dst = buildPath + "/" + targetName + "/" + fileName;
if (!File.Exists(dst)) FileUtil.CopyFileOrDirectory(src, dst); pbxProject.AddFile(targetName + "/" + fileName, fileName);
pbxProject.AddBuildProperty(targetGuid, "CODE_SIGN_ENTITLEMENTS", targetName + "/" + fileName);
pbxProject.WriteToFile(pbxProjectPath);
var plistPath = buildPath + "/Info.plist";
var plistDocument = new PlistDocument(); plistDocument.ReadFromString(File.ReadAllText(plistPath));
var rootDict = plistDocument.root;
rootDict.SetBoolean("FirebaseAppDelegateProxyEnabled", false);
PlistElementArray customDomains = rootDict.CreateArray("FirebaseDynamicLinksCustomDomains");
customDomains.AddString("https://cgs.link");
File.WriteAllText(plistPath);

Related

Google Cloud speech simple problems with no response

I have error requests. I still don't know where to invoke request and how to fetch response. Where do I set API key?
var initialize = new Google.Apis.Services.BaseClientService.Initializer();
initialize.ApiKey = "key";
var speech = new Google.Apis.Speech.v1.SpeechService(new Google.Apis.Services.BaseClientService.Initializer {
});
var recognizeReq = new Google.Apis.Speech.v1.Data.RecognizeRequest();
var recognitionConf = new Google.Apis.Speech.v1.Data.RecognitionConfig();
recognitionConf.LanguageCode = "pl-PL";
recognitionConf.SampleRateHertz = 16000;
recognitionConf.Encoding = "FLAC";
recogniseReq.Config = recognitionConf;
var aud = new Google.Apis.Speech.v1.Data.RecognitionAudio();
string path1 = #"c:\output.flac";
//var bytesAudio = File.ReadAllBytes(path1);
aud.Uri = path1;
recognizeReq.Audio = aud;
var variable = speech.Speech.Recognize(recogniseReq);
variable.Key = "key";
//variable.OauthToken =
variable.Execute();
Google.Apis.Speech.v1.Data.RecognizeResponse resp = new Google.Apis.Speech.v1.Data.RecognizeResponse();
var lista = resp.Results;
I change software and now I use Google.Cloud.Speech.V1 library
I managed to save voice using NAudio
and I tried to send continuos request to cloud, but it doesn't work
'''
waveFile.Write(e.Buffer, 0, e.BytesRecorded);
waveFile.Flush();
audio5 = RecognitionAudio.FromBytes(e.Buffer);
var result = client.LongRunningRecognizeAsync(config, audio5);
'''
This solves problem.
problem with buffer is for a longer time.
I get into trap like others.
found solution in dispute about bug (from Google of corse ;) )
https://github.com/GoogleCloudPlatform/dotnet-docs-samples/blob/95b32e683ba534883b8a7f3c979deee101ba3678/speech/api/Recognize/InfiniteStreaming.cs

How to authenticate with twitter from a firefox plugin

Echofon abandoned their firefox twitter plugin around April 2013, but it's been maintained on github until some recent changes to the twitter API broke it.
In normal use, authentication should follow PIN-based authentication, but instead the request to https://api.twitter.com/oauth/request_token is returning "{"errors":[{"code":32,"message":"Could not authenticate you."}]}'" status='401'
I think the problem is in the TwitterClient.buildOAuthHeader function
TwitterClient.buildOAuthHeader = function (user, method, url, param)
{
var ts = Math.ceil(Date.now() / 1000);
var diff = EchofonUtils.timestampDiff();
if (diff != 0) {
EchofonUtils.debug("local timestamp " + ts + " / server timetsamp " + (ts + diff));
ts += diff;
}
var converter = Cc["#mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var result = {};
var data = converter.convertToByteArray(user + Date.now() + url + Math.random(), result);
var ch = Cc["#mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
ch.init(ch.MD5);
ch.update(data, data.length);
var hash = ch.finish(false);
var s = convertToHexString(hash);
var oauthparam = {"oauth_consumer_key" : OAUTH_CONSUMER_KEY,
"oauth_timestamp" : ts,
"oauth_signature_method" : "HMAC-SHA1",
"oauth_nonce" : s + Math.random(),
"oauth_version" : "1.0"};
if (user.oauth_token) {
oauthparam["oauth_token"] = EchofonAccountManager.instance().get(user.user_id).oauth_token;
}
var dict = {};
for (var key in param) dict[key] = param[key];
for (var key in oauthparam) dict[key] = oauthparam[key];
var paramStr = encodeURLParameter(dict);
var base = [method, RFCEncoding(url), RFCEncoding(paramStr)].join("&");
var signature;
var secret = user.oauth_token_secret ? EchofonAccountManager.instance().get(user.user_id).oauth_token_secret : "";
var signature = EchofonSign.OAuthSignature(base, secret);
oauthparam['oauth_signature'] = signature;
var headers = [];
for (var key in oauthparam) {
headers.push(key + '="' + RFCEncoding(oauthparam[key]) + '"');
}
headers.sort();
return headers.join(",");
}
I've registered a new application at dev.twitter.com and I'm using the consumer key from that instead of the one in the repository.
Also, I've added the oauth_callback attribute to the oauthparam object, with the value set to "oob" as detailed in the PIN-based authentication link above, but the plugin is not authenticating correctly with the API.
What needs to be changed in the authorization header to correct this?
This issue has been resolved.
Instructions on how to install a patched version of the plugin here - https://github.com/echofox-team/echofon-firefox-unofficial/issues/85#issuecomment-581843812

Copy/rename google sheet, share & get share id with script in form

Is it possible for a script to copy/rename an (unrelated) Google spreadsheet, share it with a given email (preferably testing for the existence of a google account associated with the given email first), and save the shared url? I have spent hours and can't find any part of the answer to this: nothing on copying/renaming, nothing on share id. I may be missing some keyword or something. I realize I'll probably be downvoted for a general question, but this is the only Google Script support out there, I think. If you give me a thread, I'll follow it.
So: I had to figure this out, and finally hired someone to code this snippet for me, here's what I got. This works pretty great for me. I did a tiny bit of customization.
Here's the requirement:
Given 2 Google Sheets, GTemplate & GTracker (these are two separate spreadsheets, not tabs in the same spreadsheet). Create a form (that reports to a tab/sheet in GTracker) which a user (anyone) fills in online with an email "UserEmail", and a string "UserID". Upon submission:
1) Make a copy of GTemplate, and rename it to "GTUserID" (if GTUserID already exists, name it GTUserID1, 2, or whatever)
2) Check that the submitted email has an associated Google Account
3) Share GTUserID with UserEmail
4) Save the URL of GTUserID to GTracker
5) Send an email to UserEmail confirming success or failure of above
//************ Edit here *******************************
var stremplateID = '1PBO9KhoZa9iX3Uik-FxXGnvhgs0BoNQUJmV95UUg56o';
//******************************************************
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Initialization')
.addItem('Initialize', 'setTrigger')
.addToUi();
}
function setTrigger(){
var arr= ScriptApp.getProjectTriggers();
for (var i= arr.length; i>0; i--){
ScriptApp.deleteTrigger(arr[i-1])
}
ScriptApp.newTrigger('onFormSubmit')
.forSpreadsheet(SpreadsheetApp
.getActiveSpreadsheet().getId())
.onFormSubmit()
.create()
}
function onFormSubmit(e) {
try {
//Logger.log(JSON.stringify(e))
var folder;
var strEmail = e.namedValues.Email;
var strUID = e.namedValues['User ID'];
//Logger.log(strEmail)
//Logger.log(strUID)
var oldFile = DriveApp.getFileById(stremplateID);
var folders = oldFile.getParents();
while (folders.hasNext()) {
folder = folders.next();
break;
}
if ((typeof folder) != "object") {
folder = DriveApp.getRootFolder();
}
var bolFlag = false;
var bolFlag1 = false;
var i = 0;
var myRegexp = new RegExp('[^a-zA-Z0-9.-]','g');
var strProcUID=String(strUID).replace(myRegexp, "_");
//Logger.log(strProcUID)
var strFilename = strProcUID;
while (!bolFlag) {
var files = folder.getFiles();
while (files.hasNext()) {
var file = files.next();
if (file.getName() == strFilename) {
bolFlag1 = true;
}
}
if (!bolFlag1) {
bolFlag = true;
} else {
i = i + 1;
strFilename = strProcUID + i;
bolFlag1 = false;
}
}
var newFile = oldFile.makeCopy(strFilename, folder);
newFile.addEditors(strEmail);
var link = newFile.getUrl();
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var row = sh.getLastRow();
var col = sh.getLastColumn();
sh.getRange(row, col, 1, 1).setValue(link);
var body = 'Dear ' + strUID + ',\n' +
'Your request has been processed successfully.\n' +
'The file can be seen here:\n' +
link + '\n' +
'Regards,\n ' +
'Admin';
GmailApp.sendEmail(strEmail, 'Request Processed', body);
} catch (e) {
var body = '\n' +
'An error occurred while processing the request:\n' +
'User ID: ' + strUID + '\n ' +
'Email: ' + strEmail + '\n ' +
'Error: ' + e;
GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), 'Error processing a request', body);
var body = 'Dear ' + strUID + ',\n' +
'Sorry, an error occurred while processing your request.\n' +
'Regards,\n ' +
'Admin';
GmailApp.sendEmail(strEmail, 'Error processing the request', body);
}
}
Perhaps this will be helpful to someone else. The things it does that I couldn't find was copying a copied/shared Google Sheet URL into a different sheet (for creating daughter shared documents for different projects, initiated by the project teams, still owned by the admin account, with internal fields readily accessible since we've got the URL). I hope that's clear and helpful.

Converting docx to images using Aspose.Words

I'm converting a generated doc to a pdf and to png's, see the code below. But for some reason there is something wrong with the fonts. On my local develop machine everything is right, but when deployed on the production servers the fonts in the PNG's are missing. I've checked but they are installed on the servers. Can someone help me with this?
var dstDoc = doc.Clone();
var newInvoice = new InvoicePdf(factuur);
var ds = newInvoice.ToDataSet();
dstDoc.BuiltInDocumentProperties.Title = newInvoice.InvoiceID;
dstDoc.BuiltInDocumentProperties.Subject = newInvoice.SendDate;
dstDoc.MailMerge.FieldMergingCallback = new HandleMergeFieldAlternatingRows();
dstDoc.MailMerge.ExecuteWithRegions(ds);
var filePath = Path.Combine(folderInvoices, newInvoice.SendDateOrginal.Year.ToString(CultureInfo.InvariantCulture));
Directory.CreateDirectory(filePath);
var fileName = string.Format("{0} - {1}", newInvoice.InvoiceID, newInvoice.DebtorCompany.ToString(true));
filePath = Path.Combine(filePath, fileName);
filePaths.Add(filePath + ".pdf");
dstDoc.Save(filePath + ".pdf", SaveFormat.Pdf);
var options = new ImageSaveOptions(SaveFormat.Png) { PageCount = 1, Resolution = 120, UseAntiAliasing = true, PrettyFormat = true, UseHighQualityRendering = true };
for (var i = 0; i < dstDoc.PageCount; i++)
{
options.PageIndex = i;
dstDoc.Save(string.Format("{0}_{1}.png", filePath, i), options);
}
If it is a shared server, then most probably it is a security issue. Aspose.Words for .NET DLL needs access to the Windows registry, to find the fonts folder. Refer to http://www.aspose.com/docs/display/wordsnet/Considerations+When+Running+on+a+Shared+Server+Environment for more details.
A workaround is also possible to specify the path of folder, which has all the required fonts. Please see http://www.aspose.com/docs/display/wordsnet/How+to++Specify+True+Type+Fonts+Location for sample code.
I work with Aspose as Developer evangelist.

YouTube : This video contains content from vevo?

I am trying to play a YouTube video in my application. Everything works fine. However, when I try to watch a video that contains content from Vevo, it fails.
I had also tried to pass el=vevo in get_video_info:
http://www.youtube.com/get_video_info?video_id=uuZE_IRwLNI&el=vevo&ps=default&eurl=&gl=US&hl=en
stream
{
"fallback_host" = "tc.v12.cache7.googlevideo.com";
itag = 22;
quality = hd720;
s = "8E6E5D13EB65FB653B173B94CB0BCC3A20853F5EDE8.5E2E87DF33EEDE165FEA90109D3C7D5DADA06B6BB60";
type = "video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"";
url = "http://r7---sn-cvh7zn7r.googlevideo.com/videoplayback?pcm2fr=yes&sver=3&expire=1393773646&itag=22&id=bae644fc84702cd2&upn=SjZd81MudQs&sparams=gcr%2Cid%2Cip%2Cipbits%2Citag%2Cpcm2fr%2Cratebypass%2Csource%2Cupn%2Cexpire&ms=au&gcr=in&mt=1393747698&source=youtube&ratebypass=yes&ipbits=0&fexp=935620%2C919120%2C912523%2C932288%2C914084%2C916626%2C937417%2C937416%2C913434%2C932289%2C936910%2C936913%2C902907&mv=m&key=yt5&ip=103.250.162.79";
}
When I use url its not playing. Is there any solution?
get_video_info works only for the videos which are allowed to be viewed as embedded videos in other websites. I struggled a lot with get_video_info but could find any solution for vevo. however I was able to make it work by retrieving the actual video page, in actual video page you have to grab player version and hit url (specified in code) to grab the streams links and actual signatures.
youtube might change this in future but today following solutions is working great for me.
Its c# you should know how to convert it into object-C, entry point of following code is ExtractUrls function and remember to pass it html of video page.
e.g. html content of http://www.youtube.com/watch?v=J5iS3tULXMQ&nomobile=1
private static List<string> ExtractUrls(string html)
{
string Player_Version = Regex.Match(html, #"""\\/\\/s.ytimg.com\\/yts\\/jsbin\\/html5player-(.+?)\.js""").Groups[1].ToString();
string Player_Code = new WebClient().DownloadString("http://s.ytimg.com/yts/jsbin/" + "html5player-" + Player_Version + ".js");
html = Uri.UnescapeDataString( Regex.Match(html, #"""url_encoded_fmt_stream_map"":\s+""(.+?)""", RegexOptions.Singleline).Groups[1].ToString());
var Streams = Regex.Matches(html, #"(^url=|(\\u0026url=|,url=))(.+?)(\\u0026|,|$)");
var Signatures = Regex.Matches(html, #"(^s=|(\\u0026s=|,s=))(.+?)(\\u0026|,|$)");
List<string> urls = new List<string>();
for (int i = 0; i < Streams.Count - 1; i++)
{
string URL = Streams[i].Groups[3].ToString();
if (Signatures.Count > 0)
{
string Sign = Sign_Decipher(Signatures[i].Groups[3].ToString(), Player_Code);
URL += "&signature=" + Sign;
}
urls.Add(URL.Trim());
}
return urls;
}
public static string Sign_Decipher(string s, string Code)
{
string Function_Name = Regex.Match(Code, #"signature=(\w+)\(\w+\)").Groups[1].ToString();
var Function_Match = Regex.Match(Code, "function " + Function_Name + #"\((\w+)\)\{(.+?)\}",RegexOptions.Singleline);
string Var = Function_Match.Groups[1].ToString();
string Decipher = Function_Match.Groups[2].ToString();
var Lines = Decipher.Split(';');
for (int i = 0; i < Lines.Length; i++)
{
string Line = Lines[i].Trim();
if (Regex.IsMatch(Line, Var + "=" + Var + #"\.reverse\(\)"))
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
s = new string(charArray);
}
else if (Regex.IsMatch(Line, Var + "=" + Var + #"\.slice\(\d+\)"))
{
s = Slice(s, Convert.ToInt32(Regex.Match(Line, Var + "=" + Var + #"\.slice\((\d+)\)").Groups[1].ToString()));
}
else if (Regex.IsMatch(Line, Var + #"=\w+\(" + Var + #",\d+\)"))
{
s = Swap(s, Convert.ToInt32(Regex.Match(Line, Var + #"=\w+\(" + Var + #",(\d+)\)").Groups[1].ToString()));
}
else if (Regex.IsMatch(Line, Var + #"\[0\]=" + Var + #"\[\d+%" + Var + #"\.length\]"))
{
s = Swap(s, Convert.ToInt32(Regex.Match(Line, Var + #"\[0\]=" + Var + #"\[(\d+)%" + Var + #"\.length\]").Groups[1].ToString()));
}
}
return s;
}
private static string Slice(string Input, int Length)
{
return Input.Substring(Length, Input.Length - 1);
}
private static string Swap(string Input, int Position)
{
var Str = new StringBuilder(Input);
var SwapChar = Str[Position];
Str[Position] = Str[0];
Str[0] = SwapChar;
return Str.ToString();
}
credit goes to comments under this code project artical
Certain videos have a domain-level whitelist or blacklist applied to them. This is done at the discretion of the content owner.
If there is a whitelist or a blacklist, and the domain of the embedding site can't be determined (perhaps because of there not being a real referring domain in the case of your native application), then the default behavior is to block playback.
This blog post has a bit more detail as well: http://apiblog.youtube.com/2011/12/understanding-playback-restrictions.html
That specific video can only be played when it's embedded on a real website with a real referring URL, due to the way domain white/blacklisting works. And, we don't expose those lists via the API. It's a longstanding feature request
YouTube video URL should contain a signature (which is included in the 's' field), to use this url, you need to decrypt the signature first and add it to the URL.
The signature decryptor can be found on the web page of the video (i.e. youtube.com/watch?v=VIDEO_ID).
I can't provide more info as it would be against YouTube terms of service :).

Resources