so I have made a comments option on my app which lets the users comment something on posts. It is directly referenced and sends info from the string to the api endpoint. That works, now I was wondering if there was a way to limit the number of essentially requests to the API so users can send comments once every minute per say
TextField("Comment...", text: $comment) { editingChanged in
} onCommit: {
validate()
showsAlert = !isValid
if isValid{
viewModel.sendComment(nickname: nickname, body: comment) {
self.comment = ""
}
}
}
}
.padding()
.frame(width: UIScreen.main.bounds.width, height: 80)
.ignoresSafeArea(.keyboard, edges: .bottom)
It would help me reduce spam on the comments and would be a life saver
You can create a simple Swift class to control the time.
public class Time_Control:Thread{
var wait_time:Int //In seconds
public var can_send:Bool = true
init(_ wait_time:Int) {
self.wait_time = wait_time
}
public override func start() {
super.start()
self.can_send = false
Thread.self.sleep(forTimeInterval: TimeInterval(self.wait_time))
self.can_send = true
}
}
Then you can use this object in any part of your code to control time between events. For this, first have a variable to store the object of the class Time_Control, for example, var control:Time_Control = Time_Control(0). After that, each time you want to control the time of something just do the following:
func send_message(){
if control.can_send{
//Start the time controller with 60 seconds
control = Time_Control(60)
control.start()
//Let the user send message
//... your code
}else{
//Don't let the user send message
//...
}
}
When the user interacts with your UI, you can call the function send_message(). It will allow the user to send a message every 60 seconds.
In Swift you can get the Unix timestamp of the current moment using:
let currentUnixTimestamp = Date().timeIntervalSince1970
Unix time simply counts the seconds that have elapsed since 1 Jan, 1970 00:00:00 UTC. See Wikipedia to learn more.
Using this, you could create a field in which you save the current timestamp every time a comment is sent and then check the current time against the saved timestamp. If your viewModel is of a class type, which I assume it is, it is the perfect place to put this code. It might look like this:
class ViewModel {
// How many seconds to wait before the user can comment again
private static let cooldownInSeconds = 60
private var lastSendTimestamp: Int? = nil
func sendComment(nickname: String, body: String) {
let currentTime = Date().timeIntervalSince1970
if currentTime >= (lastSendTimestamp ?? Int.min) + ViewModel.cooldownInSeconds {
lastSendTimestamp = currentTime
// Send comment here
} else {
// Cannot send comment because cooldown has not run out
}
}
}
Note that putting the variable in structs (like your View) will not work well, due to their immutable nature.
Related
I'm trying to use Firebase and its callable Cloud Functions for my Unity project.
With the docs and different posts I found on the web I struggle to understand how returning data works. (I come from Azure Functions in C#)
I use TypeScript, and try to return a custom object CharactersResponse:
export class CharactersResponse //extends CustomResponse
{
Code!: CharactersCode;
CharacterID?: string;
}
export enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
(Custom Response is a parent class with only an UnknownErrorMessage string property, that I use for adding extra message when needed, but only in Unity. I don't need it in my functions.)
I have the same in my C# Unity Project:
public class CharactersResponse : CustomResponse
{
public CharactersCode Code;
public string CharacterID;
}
public enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
I'm still learning but I found it useful to do this way for displaying correct messages in Unity (and also regarding localization).
When the Code is 0 (Success), I will usually need to get some data at the same time like in this example CharacterID, or CharacterLevel, CharacterName etc.. CharacterResponse will be used for all functions regarding Characters like "GetAllCharacters", "CreateNewCharacter" etc..
My Function (CreateNewCharacter) looks like this:
import * as functions from "firebase-functions";
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import { CharactersResponse } from "./CharactersResponse";
import { CharactersCode } from "./CharactersResponse";
import { StringUtils } from "../Utils/StringUtils";
// DATABASE INITIALIZATION
initializeApp();
const db = getFirestore();
// CREATE NEW CHARACTER
export const CreateNewCharacter =
functions.https.onCall((data, context) =>
{
// Checking that the user is authenticated.
if (!context.auth)
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
// TEST
data.text = '';
// Authentication / user information is automatically added to the request.
const uid: string = context?.auth?.uid;
const characterName: string = data.text;
// Check if UserID is present
if (StringUtils.isNullOrEmpty(uid))
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'Missing UserID in Auth Context.');
}
const response = new CharactersResponse();
if (StringUtils.isNullOrEmpty(characterName))
{
response.Code = CharactersCode.InvalidName;
console.log("character name null or empty return");
return response; // PROBLEM IS HERE *****************
}
console.log("end return");
return "Character created is named : " + characterName + ". UID = " + uid;
});
In Unity, the function call looks like this:
private static FirebaseFunctions functions = FirebaseManager.Instance.Func;
public static void CreateNewCharacter(string text, Action<CharactersResponse> successCallback, Action<CharactersResponse> failureCallback)
{
Debug.Log("Preparing Function");
// Create the arguments to the callable function.
var data = new Dictionary<string, object>();
data["text"] = text;
// Call the function and extract the operation from the result.
HttpsCallableReference function = functions.GetHttpsCallable("CreateNewCharacter");
function.CallAsync(data).ContinueWithOnMainThread((task) =>
{
if (task.IsFaulted)
{
foreach(var inner in task.Exception.InnerExceptions)
{
if (inner is FunctionsException)
{
var e = (FunctionsException)inner;
// Function error code, will be INTERNAL if the failure
// was not handled properly in the function call.
var code = e.ErrorCode;
var message = e.Message;
Debug.LogError($"Code: {code} // Message: {message}");
if (failureCallback != null)
{
failureCallback.Invoke(new CharactersResponse()
{
Code = CharacterCode.UnknownError,
UnknownErrorMessage = $"ERROR: {code} : {message?.ToString()}"
});
}
}
}
}
else
{
Debug.Log("About to Deserialize response");
// PROBLEM IS HERE *********************
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
Debug.Log("Deserialized response");
if (response == null)
{
Debug.LogError("Response is NULL");
}
else
{
Debug.Log("ELSE");
Debug.Log($"Response: {response}");
Debug.Log(response.Code.ToString());
}
}
});
}
The problem :
In my Unity C# code, task.Result.Data contains the CharactersCode I've set in my function, but I can't find a way to convert it to CharactersResponse. (It worked in Azure Functions). Moreover, the line just after Deserialization Debug.Log("Deserialized response"); is not executed. The code seems stuck in the deserialization process.
I tried with and without extending my TypeScript class with CustomResponse(because I don't need it in my Function so I didn't extended it at first).
I also tried setting a CharacterID because I thought maybe it didn't like the fact that this property was missing but the result is the same.
I don't understand what is the problem here? If any of you can help.
Thanks.
HttpsCallableResult.Data is of type object!
=> Your ToString will simply return the type name something like
System.Object
or in your case the result is a dictionary so it prints out that type.
=> This is of course no valid JSON content and not what you expected.
Simply construct the result yourself from the data:
var result = (Dictionary<string, object>)task.Result.Data;
CharactersResponse response = new CharactersResponse
{
Code = (CharactersCode)(int)result["Code"],
CharacterID = (string)result["CharacterID"];
};
I wanted to implement derHugo's solution but couldn't find a way to convert task.Result.Data to Dictionary<string, object>.
The code was stuck at var result = (Dictionary<string, object>)task.Result.Data; even in step by step debugging and no error popped up.
OLD SOLUTION:
So I did a little research and stumbled upon this post and ended up using this instead :
var json = JsonConvert.SerializeObject(task.Result.Data);
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(json);
I basically convert the task.Result.Data to JSON and convert it back to CharactersResponse and it works. I have what I wanted.
However, I seem to understand that it is not the best solution performance-wise, but for now it is okay and I can now move forward in the project, I'll try to find a better solution later.
NEW SOLUTION:
I wanted to try one last thing, out of curiosity. I wondered what if I convert to JSON at the beginning (in my function) instead of at the end (in my Unity app). So I did this in my function's TypeScript code:
response.Code = CharactersCode.InvalidName;
var r = JSON.stringify(response); // Added this line
return r; // return 'r' instead of 'response'
In my C# code, I retried this line of code:
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
And it works ! I just needed to convert my object to JSON in my function before returning it. It allows me to "save" one line of code to process on the client side compared to the old solution.
Thanks derHugo for your answer as it helped me finding what I want.
I suppose that the answer will be very obvious, but still it evades me. I'm new on working with observables, and now I'm facing issues assigning a value from one. I had success if I define it (this._apps) as an Observable and asking from the view to the service using subscribe (But for my taste is was way convoluted (three levels inside a map just to return another observable with the array and then another function to subscribe the previous to assign the variable and another subscription in the view to finally show the information), inefficient and on top of that I could not get it "right" again). The task is very simple. Given the class Application
export class Application {
name: string;
baseUrl: string;
deprecated: boolean;
}
And the service (just the relevant code)
private _apps: Application[] = [];
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('Apps subscriber');
this._apps = apps;
console.log('Apps subscriber Ends ' + apps);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
private getAllApplications() {
return this._http.get<Application[]>('http://development:4300/api/v1/apps');
}
From the constructor the function which gets the information from WebAPI is triggered, and the remote call is successful, but the variable this._apps is an empty array if I try to call it from anywhere in the code. I could not determine the type of the parameter "apps" in the subscribe function, but for some reason it cannot be assigned and the best answer given is that it is a function (See my first update) in one of my tries. Currently it returns in the console "[object Object]", but apps[0] gives undefined, so it is an empty Array.
This is the console output, just starting the application:
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Refreshing apps cache calling http://development:4300/api/v1/atbc-apps
Apps subscriber
Apps subscriber Ends [object Object]
I was trying this solution among many others that I forget (to use the more modern HttpClient instead the Http I used before), so what I'm doing wrong?
Update 1
I changed the constructor to this:
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('apps length ' + apps.length);
this._apps = apps; // Remember private _apps: Application[] = [];
console.log('Apps subscriber Ends ' + apps.toString);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
and the declaration of the function called into this:
private getAllApplications(): Observable<Application[]> {
// the exactly the same as before
}
And now I got from the console this:
apps length undefined
Apps subscriber Ends
function () {
if (this instanceof Promise) {
return PROMISE_OBJECT_TO_STRING;
}
return originalObjectToString.apply(this, arguments);
}
That is the function I was talking about. Any ideas about why even though there is no errors (nor at compile time, neither at runtime), the returning object is not a real Application array?
Change this line:
private _apps: Application[] = [];
to:
_apps: Application[] = [];
Which will default to making it public. Then this line will see it:
this._apps = apps;
At the end I suppose is a mindset to work with Observables, and I tried to build a kind of cache, so the only way I could do it (let me know if there is a better way) was using the view to fill-out the cache. I could not do it from the service itself because the calling the function from the view is synchronous and to fill out the array is async. So I had to create a public setApplicationCache procedure which is filled out after calling the service from the view, it call the setApplicationCache( Application[] ) function and the rest works because it takes just the cache to do filtering and other operations or use it from other pages w/o calling the database again and again.
This is the code from the first view called (main page)
ngOnInit() {
this._myService.getAllApplications().subscribe(arrObjApps => {
this._myService.setApplicationsCache(arrObjApps)
this.listApps = this._myService.getApplications(true);
});
And the service has this functions:
private _apps: Application[] = [];
getAllApplications(): Observable<Application[]> {
return this._http.get('http://development:4300/api/v1/atbc-apps').pipe(
map( (response: Response) => {
let results = response.json().data.map( app => {
return new Application(app.name, app.baseUrl, app.deprecated);
});
return results;
})
);
}
getApplication(appName: string): Application {
return this._apps.find(app => app.name == appName);
}
getApplications(onlyActives: boolean): Application[] {
if (onlyActives) {
return this._apps.filter(app => app.deprecated == false);
} else {
return this._apps;
}
}
And as I stated the solution should be obvious. Just again the async mindset required to work with observables.
So I am trying to make a reminder chatbot and the code I made is not working as well as I expected.
What I did was, for example, if I were to send a reminder after a day, response conditions would be like the following:
"conditions": now().reformatDateTime().toInt()+1
then followed by output-text-value as usual.
Is this valid? Any kinds of suggestion will be more than welcome. Thanks!
You user need to call the day for you get before you did something.
Active your sys entitie: #sys-date for get days.
And, for example, user will ask:
What I need to do today?
Save the day with one context variable, like:
{
"context": {
"verifiedDate": <? #sys-date ?>;
},
"output": {
"text": {
"values": [
"Please wait and I'll verified your request."
],
"selection_policy": "sequential"
}
}
}
And, only if user asks, you will do something within your application for remember the user.
Like:
Example (app.js):
function updateMessage(input, data, req, res) {
if (data.context.verifiedDate){
searchRequest(data, req, res);
} else if (data.output && data.output.text) {
return res.json(data);
}
return data;
}
You can use the data for sending something within conversation flow.
function searchRequest(data, req, res){
// something to do and return value
var sendRequest = "Thanks for wait, the request is" + valueRequest;
data.output.text[0] = sendRequest;
return data;
}
This example is with Nodejs, data have all return from Watson Conversation, like entities, intents, context variables, node flows, etc. The logic is the same, you need to access the data returned from your conversation, and after, access the context variables to do something within your app.
Add days:
Date.prototype.addDays = function(days) {
var dat = new Date(this.valueOf());
dat.setDate(dat.getDate() + days);
return dat;
}
var dat = new Date();
alert(dat.addDays(5))
You can add days with the days parameter or with one number, like my example.
In an effort to properly instantiate Typescript objects from data received over HTTP as JSON, I was exploring the possibility of using the for..in loop coupled with .hasOwnProperty() like so:
class User {
private name: string;
private age: number;
constructor(data: JSON) {
console.log('on constructor\ndata', data);
for (var key in data) {
console.log('key:', key);
if (User.hasOwnProperty(key)) {
console.log('User has key:', key);
this[key] = data[key];
}
}
}
displayInfo(): string{
return JSON.stringify(this);
}
}
let button = document.createElement('button');
button.textContent = "Test";
button.onclick = () => {
try{
let json = JSON.parse('{"name": "Zorro","age": "24"}');
let usr = new User(json);
console.log(usr.displayInfo());
}catch (error){
alert(error);
}
}
document.body.appendChild(button);
Using similar code in my project fails completely. That is expected as the compiled JS code has no awareness of the private TS vars and so, hasOwnProperty is always false.
However, I was using the Typescript Playground, and running that code there produces the following output in the console:
on constructor
data Object {name: "Zorro", age: "24"}
key: name
User has key: name
key: age
{"name":"Zorro"}
As you can see, there are clearly some unexpected things happening here. The first key is recognized and the new User instance is initialized with the value from the JSON, yet that does not happen for the second key.
Can someone explain why this happens?
As was pointed out in the comments, you should be using this.hasOwnProperty instead of User.hasOwnProperty. And as you noticed, this code is busted anyway because the property declarations in the class don't actually create own properties on the object (they would need to be initialized for this to happen).
But why did you get a hit on the name key? The User object is a constructor function for your class. Functions do have a name property:
function fn() { }
console.log(fn.name); // prints 'fn'
They don't have an age property, of course.
Your constructor would of course just have to look like this, if you want to construct User instances from plain JavaScript objects:
constructor(data: any) {
this.name = data.name;
this.age = data.age;
}
i am trying to detect new emails in inbox using subscribeToPullNotifications as follows:
PullSubscription subscription = service.subscribeToPullNotifications(
folder, 1, null, EventType.NewMail);
GetEventsResults events = subscription.getEvents();
System.out.println("####### EVENTS: "
+ events.getItemEvents().toString());
for (ItemEvent itemEvent : events.getItemEvents()) {
if (itemEvent.getEventType() == EventType.NewMail) {
EmailMessage message = EmailMessage.bind(service,
itemEvent.getItemId());
System.out.println("######## NEW EMAIL MESSAGE IS: "
+ message.getSubject());
}
}
but the events.getItemEvents() is always empty, even i can see new emails in the inbox.
also how to make the above code is always repeated while the application is running, so that each minute it check for new emails.
Here it depends on when you are calling this, if suppose you are calling this as particular interval then you need to pass "WaterMark" of previous response in new request, else all events which occurred in between would be lost.
method : subscription.getWaterMark()
need to pass this as thrid argument to method subscribeToPullNotifications()
else you can continously pull on the same service by placing that in loop :
while (true) {
GetEventsResults events = null;
try {
events = subscription.getEvents();
} catch (Exception e1) {
e1.printStackTrace();
}
for (ItemEvent itemEvent : events.getItemEvents()) {
// do something...
}
}
But this would continuously pull from server increasing load, so rather use first approach by subscribing at regular interval, and passing previous water-mark in request.