Moving a chat server application from parse.com to google app engine - android

We are planning to move a chat server application resides on parse.com to Google app engine's datastore since parse is going to shutdown it's service on Jan 2017. I think this should be possible through App engine's XMPP API. Not sure, I love to hear from you..
Currently I'm testing with this code provided by Google
# Copyright 2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Crowdguru sample application using the XMPP service on Google App Engine."""
import datetime
from google.appengine.api import datastore_types
from google.appengine.api import xmpp
from google.appengine.ext import ndb
from google.appengine.ext.webapp import xmpp_handlers
import webapp2
from webapp2_extras import jinja2
PONDER_MSG = 'Hmm. Let me think on that a bit.'
TELLME_MSG = 'While I\'m thinking, perhaps you can answer me this: {}'
SOMEONE_ANSWERED_MSG = ('We seek those who are wise and fast. One out of two '
'is not enough. Another has answered my question.')
ANSWER_INTRO_MSG = 'You asked me: {}'
ANSWER_MSG = 'I have thought long and hard, and concluded: {}'
WAIT_MSG = ('Please! One question at a time! You can ask me another once you '
'have an answer to your current question.')
THANKS_MSG = 'Thank you for your wisdom.'
TELLME_THANKS_MSG = THANKS_MSG + ' I\'m still thinking about your question.'
EMPTYQ_MSG = 'Sorry, I don\'t have anything to ask you at the moment.'
HELP_MSG = ('I am the amazing Crowd Guru. Ask me a question by typing '
'\'/tellme the meaning of life\', and I will answer you forthwith! '
'To learn more, go to {}/')
MAX_ANSWER_TIME = 120
class IMProperty(ndb.StringProperty):
"""A custom property for handling IM objects.
IM or Instant Message objects include both an address and its protocol. The
constructor and __str__ method on these objects allow easy translation from
type string to type datastore_types.IM.
"""
def _validate(self, value):
"""Validator to make sure value is an instance of datastore_types.IM.
Args:
value: The value to be validated. Should be an instance of
datastore_types.IM.
Raises:
TypeError: If value is not an instance of datastore_types.IM.
"""
if not isinstance(value, datastore_types.IM):
raise TypeError('expected an IM, got {!r}'.format(value))
def _to_base_type(self, value):
"""Converts native type (datastore_types.IM) to datastore type (string).
Args:
value: The value to be converted. Should be an instance of
datastore_types.IM.
Returns:
String corresponding to the IM value.
"""
return str(value)
def _from_base_type(self, value):
"""Converts datastore type (string) to native type (datastore_types.IM).
Args:
value: The value to be converted. Should be a string.
Returns:
String corresponding to the IM value.
"""
return datastore_types.IM(value)
class Question(ndb.Model):
"""Model to hold questions that the Guru can answer."""
question = ndb.TextProperty(required=True)
asker = IMProperty(required=True)
asked = ndb.DateTimeProperty(required=True, auto_now_add=True)
suspended = ndb.BooleanProperty(required=True)
assignees = IMProperty(repeated=True)
last_assigned = ndb.DateTimeProperty()
answer = ndb.TextProperty(indexed=True)
answerer = IMProperty()
answered = ndb.DateTimeProperty()
#staticmethod
#ndb.transactional
def _try_assign(key, user, expiry):
"""Assigns and returns the question if it's not assigned already.
Args:
key: ndb.Key: The key of a Question to try and assign.
user: datastore_types.IM: The user to assign the question to.
expiry: datetime.datetime: The expiry date of the question.
Returns:
The Question object. If it was already assigned, no change is made.
"""
question = key.get()
if not question.last_assigned or question.last_assigned < expiry:
question.assignees.append(user)
question.last_assigned = datetime.datetime.now()
question.put()
return question
#classmethod
def assign_question(cls, user):
"""Gets an unanswered question and assigns it to a user to answer.
Args:
user: datastore_types.IM: The identity of the user to assign a
question to.
Returns:
The Question entity assigned to the user, or None if there are no
unanswered questions.
"""
question = None
while question is None or user not in question.assignees:
# Assignments made before this timestamp have expired.
expiry = (datetime.datetime.now()
- datetime.timedelta(seconds=MAX_ANSWER_TIME))
# Find a candidate question
query = cls.query(cls.answerer == None, cls.last_assigned < expiry)
# If a question has never been assigned, order by when it was asked
query = query.order(cls.last_assigned, cls.asked)
candidates = [candidate for candidate in query.fetch(2)
if candidate.asker != user]
if not candidates:
# No valid questions in queue.
break
# Try and assign it
question = cls._try_assign(candidates[0].key, user, expiry)
# Expire the assignment after a couple of minutes
return question
#ndb.transactional
def unassign(self, user):
"""Unassigns the given user from this question.
Args:
user: datastore_types.IM: The user who will no longer be answering
this question.
"""
question = self.key.get()
if user in question.assignees:
question.assignees.remove(user)
question.put()
#classmethod
def get_asked(cls, user):
"""Returns the user's outstanding asked question, if any.
Args:
user: datastore_types.IM: The identity of the user asking.
Returns:
An unanswered Question entity asked by the user, or None if there
are no unanswered questions.
"""
query = cls.query(cls.asker == user, cls.answer == None)
return query.get()
#classmethod
def get_answering(cls, user):
"""Returns the question the user is answering, if any.
Args:
user: datastore_types.IM: The identity of the user answering.
Returns:
An unanswered Question entity assigned to the user, or None if there
are no unanswered questions.
"""
query = cls.query(cls.assignees == user, cls.answer == None)
return query.get()
def bare_jid(sender):
"""Identify the user by bare jid.
See http://wiki.xmpp.org/web/Jabber_Resources for more details.
Args:
sender: String; A jabber or XMPP sender.
Returns:
The bare Jabber ID of the sender.
"""
return sender.split('/')[0]
class XmppHandler(xmpp_handlers.CommandHandler):
"""Handler class for all XMPP activity."""
def unhandled_command(self, message=None):
"""Shows help text for commands which have no handler.
Args:
message: xmpp.Message: The message that was sent by the user.
"""
message.reply(HELP_MSG.format(self.request.host_url))
def askme_command(self, message=None):
"""Responds to the /askme command.
Args:
message: xmpp.Message: The message that was sent by the user.
"""
im_from = datastore_types.IM('xmpp', bare_jid(message.sender))
currently_answering = Question.get_answering(im_from)
question = Question.assign_question(im_from)
if question:
message.reply(TELLME_MSG.format(question.question))
else:
message.reply(EMPTYQ_MSG)
# Don't unassign their current question until we've picked a new one.
if currently_answering:
currently_answering.unassign(im_from)
def text_message(self, message=None):
"""Called when a message not prefixed by a /cmd is sent to the XMPP bot.
Args:
message: xmpp.Message: The message that was sent by the user.
"""
im_from = datastore_types.IM('xmpp', bare_jid(message.sender))
question = Question.get_answering(im_from)
if question:
other_assignees = question.assignees
other_assignees.remove(im_from)
# Answering a question
question.answer = message.arg
question.answerer = im_from
question.assignees = []
question.answered = datetime.datetime.now()
question.put()
# Send the answer to the asker
xmpp.send_message([question.asker.address],
ANSWER_INTRO_MSG.format(question.question))
xmpp.send_message([question.asker.address],
ANSWER_MSG.format(message.arg))
# Send acknowledgement to the answerer
asked_question = Question.get_asked(im_from)
if asked_question:
message.reply(TELLME_THANKS_MSG)
else:
message.reply(THANKS_MSG)
# Tell any other assignees their help is no longer required
if other_assignees:
xmpp.send_message([user.address for user in other_assignees],
SOMEONE_ANSWERED_MSG)
else:
self.unhandled_command(message)
def tellme_command(self, message=None):
"""Handles /tellme requests, asking the Guru a question.
Args:
message: xmpp.Message: The message that was sent by the user.
"""
im_from = datastore_types.IM('xmpp', bare_jid(message.sender))
asked_question = Question.get_asked(im_from)
if asked_question:
# Already have a question
message.reply(WAIT_MSG)
else:
# Asking a question
asked_question = Question(question=message.arg, asker=im_from)
asked_question.put()
currently_answering = Question.get_answering(im_from)
if not currently_answering:
# Try and find one for them to answer
question = Question.assign_question(im_from)
if question:
message.reply(TELLME_MSG.format(question.question))
return
message.reply(PONDER_MSG)
class XmppPresenceHandler(webapp2.RequestHandler):
"""Handler class for XMPP status updates."""
def post(self, status):
"""POST handler for XMPP presence.
Args:
status: A string which will be either available or unavailable
and will indicate the status of the user.
"""
sender = self.request.get('from')
im_from = datastore_types.IM('xmpp', bare_jid(sender))
suspend = (status == 'unavailable')
query = Question.query(Question.asker == im_from,
Question.answer == None,
Question.suspended == (not suspend))
question = query.get()
if question:
question.suspended = suspend
question.put()
class LatestHandler(webapp2.RequestHandler):
"""Displays the most recently answered questions."""
#webapp2.cached_property
def jinja2(self):
"""Cached property holding a Jinja2 instance.
Returns:
A Jinja2 object for the current app.
"""
return jinja2.get_jinja2(app=self.app)
def render_response(self, template, **context):
"""Use Jinja2 instance to render template and write to output.
Args:
template: filename (relative to $PROJECT/templates) that we are
rendering.
context: keyword arguments corresponding to variables in template.
"""
rendered_value = self.jinja2.render_template(template, **context)
self.response.write(rendered_value)
def get(self):
"""Handler for latest questions page."""
query = Question.query(Question.answered > None).order(
-Question.answered)
self.render_response('latest.html', questions=query.fetch(20))
APPLICATION = webapp2.WSGIApplication([
('/', LatestHandler),
('/_ah/xmpp/message/chat/', XmppHandler),
('/_ah/xmpp/presence/(available|unavailable)/', XmppPresenceHandler),
], debug=True)
If a user selects another user whom he wants to chat with should call API url /_ah/xmpp/message/chat/ which automatically invokes XmppHandler handler.
('/_ah/xmpp/message/chat/', XmppHandler)
And my doubt is, If he post a message like foo on that particular chat does it automatically invokes text_message method exists in XmppHandler ? Is there we need to configure xmpp on client side also?

For the client api compatible and db migration, you can host your own parse-server.
There is a simple express project to use parse-server.
https://github.com/ParsePlatform/parse-server-example
They are lots of deploy guide to each cloud-platform
Google App Engine
Heroku and mLab
AWS and Elastic Beanstalk
Digital Ocean
NodeChef
Microsoft Azure
Pivotal Web Services
Back4app
Or you can host your nodejs server with your domain name.
If you want to do something different from parse, you can send a pull request to parse-server. LiveQuery is the extra function created by contributors.
For more details, see the link from Parse.com , github wiki, and community links.

Parse has provided detailed information regarding the migration
process and how to move our app from their serviers to a seperately
hosted mongoDB instance and cloud company. Parse suggests the
migration be made in two steps:
The database is migrated to a service like MongoLab or ObjectRocket.
The server be migrated to a cloud hosting company like AWS, Google App Engine, or Heroku.
Parse has also put suggested deadlines in place:
they suggest that by April 28, 2016 the database be migrated and by
July 28, 2016, the server be migrated properly.
This will give you ample time to work out any bugs and ensure your app functions properly with no downtime!
A Backend-as-a-Service like parse.com coupled two aspects of what’s known as a backend into one: the server and database. The server, which manipulates the database, performs queries, fetches information, and other work-intensive tasks, interacts with the database. The two work hand-in-hand to form a backend.
With Parse going away, we must deal with the server and database
seperately.
Parse has already provided detailed info and easy migration tool for database to Mongodb hosted on any cloud.
Also it is easy to setup the node based parse server on any cloud platform including Google App Engine:
The easiest way to get Parse server running on Google Cloud is to start with the sample out on GitHub.

Related

Tealium - tracking in Kotlin doesn't report events

According to GitHub sample project and Tealium's documentation for Kotlin I created such TealiumHelper:
object TealiumHelper {
fun init(application: Application) {
val tealiumConfig = TealiumConfig(
application,
accountName = BuildConfig.TEALIUM_ACCOUNT_NAME,
profileName = BuildConfig.TEALIUM_PROFILE_NAME,
environment = BuildConfig.TEALIUM_ENVIRONMENT
)
// Display logs only for DEV
Logger.Companion.logLevel = BuildConfig.TEALIUM_LOGLEVEL
// Make it start working
Tealium.create(BuildConfig.TEALIUM_INSTANCE, tealiumConfig)
}
fun trackEvent(name: String, data: Map<String, Any>? = null) {
val eventDispatch = TealiumEvent(name, data)
Tealium[BuildConfig.TEALIUM_INSTANCE]?.track(eventDispatch)
}
fun trackView(name: String, data: Map<String, Any>? = null) {
val viewDispatch = TealiumView(name, data)
Tealium[BuildConfig.TEALIUM_INSTANCE]?.track(viewDispatch)
}
}
I get logs by Tealium so it should be working fine.
2021-05-17 14:28:56.694 22812-22894/xxx.xxx.xxx D/Tealium-1.2.2: Dispatch(fc5c0) - Ready - {tealium_event_type=view, tealium_event=XXX ...}
But after I call trackView or trackEvent, my events don't go to server.
There is also additional log infor which I don't know what does it mean. Documentation doesn't say much about it:
2021-05-17 14:28:59.352 22812-22894/xxx.xxx.xxx I/Tealium-1.2.2: Asset not found (tealium-settings.json)
How could I fix it? What does Asset not found mean?
#deadfish I manage the mobile team here at Tealium, so I can point you in the right direction. You can safely ignore the Asset Not Found log - this just indicates that no local settings file was found, so by default, the remote settings will be used. We'll look at addressing this to make the log message a little more helpful. There are a couple of things that might cause data not to be sent. Firstly, I can't see any Dispatchers specified in your TealiumConfig object. As specified in the docs, you need to add either the TagManagement or Collect dispatcher, which are the modules that send the data from the device. The Collect module sends data to the server-side platform (I think this is what you need), while the TagManagement module processes the data client-side using JavaScript. Please feel free to get in touch with our support team or contact us by raising an issue on our GitHub repo if you need further personalized support.

ANDROID VOLLEY + JWT TOKEN AUTHENTICATION + DJANGO REST FRAMEWORK

I am currently developing an android chat app. I am very new to Android Studio, JWT Token Authorization, and Django Rest Framework. Right now I am having issue to work on the Django side.
So basically I was setting up a login page from my Android, and I want it to login using phone number and password as the needed credentials. However, I also want to use JWT Token Auth to make my application more secure.
Currently I have my project urls.py pointing to one of the JWT Token API
urls.py
from django.contrib import admin
from django.urls import path,include
from django.conf.urls import include, url
from rest_framework_simplejwt import views as jwt_views
urlpatterns = [
path('admin/', admin.site.urls),
path('account/',include('restaccount.urls')) ,
path('api/token/', jwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
]
This would lead to the server page which was
*PS : The phone number fields should be the default username field..(I have made some trial modifications on my code prior I post this).
I also have set up a models that was inherit from AbstractUser
models.py
class RegisterUser(AbstractUser):
phone_number = PhoneField(name='phone_number',unique=True)
birthday = models.DateField(name ='birthday',null= True)
nickname = models.CharField(max_length=100,name = 'nickname')
def __str__(self):
return self.phone_number
Currently I have tried to make a lot of modifications to my model, like :
change username = None
REQUIRED_FIELDS = []
USERNAME_FIELDS = 'phone_number'
I realize that the Token Obtain Pair View is following the Django Administration page in terms of the information that you needed (username and password).
However when I modified, I try to create superuser and try to login too Django Admin with my modified data..But I still cannot log in.. Also, I try to get token from the superuser that I have made, but it will response in "detail": "No active account found with the given credentials"
Can somebody enlighten me of the steps that I should take now?? I have tried to look for solutions but none of them solve my problem
Here's the point TLDR:
I want my app to Login using phone number and password and want to use JWT Token Auth to make it secure.
I realize the ObtainTokenPair view follows Django Admin credentials, so I have tried to modify my backend to be "log in" using phone number and password.
After I modified, I can't login to Django Admin and cannot get token with the superuser I created.
Here some of the related file attach:
Settings.py
"""
Django settings for androidapp project.
Generated by 'django-admin startproject' using Django 3.0.7.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '6qdk058^8b2#-pnw!cr1pbd(sao)vj+v69&4874zjh95xu7pg)'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['172.31.120.211',]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'restaccount',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'androidapp.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'androidapp.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'Orbital',
'USER' :'SomeUser',
'PASSWORD':'Pass',
'HOST' : 'localhost',
'PORT' : '',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = False
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
AUTH_USER_MODEL = 'restaccount.RegisterUser'
#FORMAT FOR DATE INPUT
DATE_INPUT_FORMATS = ('%d-%m-%Y', '%d/%m/%Y', '%d/%m/%y', '%d %b %Y',
'%d %b, %Y', '%d %b %Y', '%d %b, %Y', '%d %B, %Y',
'%d %B %Y')
#Format for date-time input format
DATETIME_INPUT_FORMATS = ('%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M', '%d/%m/%Y',
'%d/%m/%y %H:%M:%S', '%d/%m/%y %H:%M', '%d/%m/%y',
'%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d')
# Adding REST_FRAMEWORK SETTING WITH JWT AUTHENTICATION
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
# AUTHENTICATION_BACKENDS = (
# 'django.contrib.auth.backends.ModelBackend',
# 'restaccount.backends.UserBackend'
# )
RegisterUserManager inside models.py
class RegisterUserManager(BaseUserManager):
def create_user(self, phone_number,password, **extra_fields):
if not phone_number:
raise ValueError('The phone number must be set')
user = self.model(
phone_number=phone_number,
password = password,
**extra_fields)
user.save()
return user
def create_superuser(self,phone_number,password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
# print(phone_number)
if extra_fields.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))
return self.create_user(phone_number, password,**extra_fields)
Its quite difficult to pin point the bug without getting hands-on to the actual project. I can't not find the bug or fix your project. that you have to do on your own. But I can surely share what I think would help you avoid bug and fix your project.
what I could understand
you want a custom user model
your want to use jwt authentication
so, let's begin. User model and authentication are two different things. create Custom User model first.
firstly, remove all users from database
create Custom User model following this 'A full example' exactly (check by creating superuser if custom user model is working properly, if not that means you missed something try again)
If you have successfully created custom user model that means you now have substituted 'username' with 'phone number' (in your case)
for authentication you can use custom authentication or as you tried you can use existing packages. Configure it to act as default authentication backend.
your choice of authentication package should take username and password, check if there is a user match those credentials create a token and return that token. you don't need to modify the authentication process you just provide username field(phone number) and password. Now here you might need to do something like
{username: phone_number, password: password}
because your authentication package might not support custom user.
hope it helps.

How to Authenticate with Alexa Voice Service from Android?

I am trying to connect to Alexa Voice Service from an Android app following the directions on this page: https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/docs/authorizing-your-alexa-enabled-product-from-an-android-or-ios-mobile-app
Bundle options = new Bundle();
String scope_data = "{\"alexa:all\":{\"productID\":\"" + PRODUCT_ID +
                    "\", \"productInstanceAttributes\": {\"deviceSerialNumber\":\"" + PRODUCT_DSN + "\"}}}";
options.putString(AuthzConstants.BUNDLE_KEY.SCOPE_DATA.val, scope_data);
options.putBoolean(AuthzConstants.BUNDLE_KEY.GET_AUTH_CODE.val, true);
options.putString(AuthzConstants.BUNDLE_KEY.CODE_CHALLENGE.val, CODE_CHALLENGE);
options.putString(AuthzConstants.BUNDLE_KEY.CODE_CHALLENGE_METHOD.val, "S256");
mAuthManager.authorize(APP_SCOPES, options, new AuthorizeListener());
First, I don't know what APP_SCOPES should be. I set it to:
protected static final String[] APP_SCOPE = new String[]{"profile", "postal_code"};
but I get an error from the server
AuthError cat= INTERNAL type=ERROR_SERVER_REPSONSE - com.amazon.identity.auth.device.AuthError: Error=invalid_scope error_description=An unknown scope was requested
What am I doing wrong and how can I do this right?
The APP_SCOPE is : "alexa:all"
The PRODUCT_DSN can be anything you want, "1234" as per suggestion from Joshua Frank (https://forums.developer.amazon.com/forums/message.jspa?messageID=18973#18973)
The PRODUCT_ID is the ID in the AVS Developper Portal (https://developer.amazon.com/edw/home.html#/avs/list)
The CODE_CHALLENGE the Client Secret in the Security Profile of your application (should be already hashed in S256)
The problem is not with the APP_SCOPES variable, it is actually with the PRODUCT_ID, PRODUCT_DSN variables passed in the scope data.
I faced this exact same issue and have raised a query in amazon developers forum on what needs to be passed in those variables - Alexa authentication issue using beta SDK
Once the PRODUCT_ID, PRODUCT_DSN & CODE_CHALLENGE variables are determined then the authentication should be pretty much straight forward.
The APP_SCOPE should be "alexa:all"

Google currency api in android [duplicate]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking us to recommend or find a tool, library or favorite off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it.
Closed 8 years ago.
This question's answers are a community effort. Edit existing answers to improve this post. It is not currently accepting new answers or interactions.
Now, I did find the Google Finance API and started looking through that but I found a lot of info about portfolios, transactions, positions & other stuff I know nothing about.
Am I looking at the wrong docs? What do I need to do to get a feed of exchange rates from GF? Is this even possible?
EDIT
To make things a little clearer. I'm not interested in technical stuff nor do I want any code.
Thanks for all your answers.
Free currencyconverterapi:
Rates updated every 30 min
API key is now required for the free server.
A sample conversion URL is: http://free.currencyconverterapi.com/api/v5/convert?q=EUR_USD&compact=y
For posterity here they are along with other possible answers:
Yahoo finance API Discontinued 2017-11-06###
Discontinued as of 2017-11-06 with message
It has come to our attention that this service is being used in
violation of the Yahoo Terms of Service. As such, the service is being
discontinued. For all future markets and equities data research,
please refer to finance.yahoo.com.
Request: http://finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s=USDINR=X
This CSV was being used by a jQuery plugin called Curry. Curry has since (2017-08-29) moved to use fixer.io instead due to stability issues.
Might be useful if you need more than just a CSV.
(thanks to Keyo) Yahoo Query Language lets you get a whole bunch of currencies at once in XML or JSON. The data updates by the second (whereas the European Central Bank has day old data), and stops in the weekend. Doesn't require any kind of sign up.
http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.xchange where pair in ("USDEUR", "USDJPY", "USDBGN", "USDCZK", "USDDKK", "USDGBP", "USDHUF", "USDLTL", "USDLVL", "USDPLN", "USDRON", "USDSEK", "USDCHF", "USDNOK", "USDHRK", "USDRUB", "USDTRY", "USDAUD", "USDBRL", "USDCAD", "USDCNY", "USDHKD", "USDIDR", "USDILS", "USDINR", "USDKRW", "USDMXN", "USDMYR", "USDNZD", "USDPHP", "USDSGD", "USDTHB", "USDZAR", "USDISK")&env=store://datatables.org/alltableswithkeys
Here is the YQL query builder, where you can test a query and copy the url: (NO LONGER AVAILABLE)
http://developer.yahoo.com/yql/console/?q=show%20tables&env=store://datatables.org/alltableswithkeys#h=select%20*%20from%20yahoo.finance.xchange%20where%20pair%20in%20%28%22USDMXN%22%2C%20%22USDCHF%22%29
Open Source Exchange Rates API
Free for personal use (1000 hits per month)
Changing "base" (from "USD") is not allowed in Free account
Requires registration.
Request: http://openexchangerates.org/latest.json
Response:
<!-- language: lang-js -->
{
"disclaimer": "This data is collected from various providers ...",
"license": "all code open-source under GPL v3 ...",
"timestamp": 1323115901,
"base": "USD",
"rates": {
"AED": 3.66999725,
"ALL": 102.09382091,
"ANG": 1.78992886,
// 115 more currency rates here ...
}
}
currencylayer API
Free Plan for 250 monthly hits
Changing "source" (from "USD") is not allowed in Free account
Requires registration.
Documentation: currencylayer.com/documentation
JSON Response:
<!-- language: lang-js -->
{
[...]
"timestamp": 1436284516,
"source": "USD",
"quotes": {
"USDAUD": 1.345352401,
"USDCAD": 1.27373397,
"USDCHF": 0.947845302,
"USDEUR": 0.91313905,
"USDGBP": 0.647603397,
// 168 world currencies
}
}
CurrencyFreaks API
Free Plan (1000 hits per month)
Changing 'Base' (From 'USD') is not allowed in free account
Requires registration
Data updated every 60 sec.
179 currencies worldwide including currencies, metals, and cryptocurrencies
Support (Even on the free plan) Shell,Node.js, Java, Python, PHP, Ruby, JS, C#, C, Go, Swift.
Documentation: https://currencyfreaks.com/documentation.html
Endpoint:
$ curl 'https://api.currencyfreaks.com/latest?apikey=YOUR_APIKEY'
JSON Response:
{
"date": "2020-10-08 12:29:00+00",
"base": "USD",
"rates": {
"FJD": "2.139",
"MXN": "21.36942",
"STD": "21031.906016",
"LVL": "0.656261",
"SCR": "18.106031",
"CDF": "1962.53482",
"BBD": "2.0",
"GTQ": "7.783265",
"CLP": "793.0",
"HNL": "24.625383",
"UGX": "3704.50271",
"ZAR": "16.577611",
"TND": "2.762",
"CUC": "1.000396",
"BSD": "1.0",
"SLL": "9809.999914",
"SDG": 55.325,
"IQD": "1194.293591",
.
.
.
[179 currencies]
}
}
Fixer.io API (European Central Bank data)
Free Plan for 1,000 monthly hits
Changing "source" (from "USD") is not allowed in Free account
Requires registration.
This API endpoint is deprecated and will stop working on June 1st, 2018. For more information please visit: https://github.com/fixerAPI/fixer#readme)
Website : http://fixer.io/
Example request :
[http://api.fixer.io/latest?base=USD][7]
Only collects one value per each day
European Central Bank Feed
Docs:
http://www.ecb.int/stats/exchange/eurofxref/html/index.en.html#dev
Request: http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml
XML Response:
<!-- language: lang-xml -->
<Cube>
<Cube time="2015-07-07">
<Cube currency="USD" rate="1.0931"/>
<Cube currency="JPY" rate="133.88"/>
<Cube currency="BGN" rate="1.9558"/>
<Cube currency="CZK" rate="27.100"/>
</Cube>
exchangeratesapi.io
According to the website: Exchange rates API is a free service for current and historical foreign exchange rates published by the European Central Bank
This service is compatible with fixer.io and is really easy to use: no API key needed - UPDATE: API key is now needed, free tier is 250 requests/mo.
For example (this uses CURL, but you can use your favorite requesting tool):
> curl https://api.exchangeratesapi.io/latest?base=GBP&symbols=USD&apikey=YOUR_KEY
{"base":"GBP","rates":{"USD":1.264494191},"date":"2019-05-29"}
CurrencyApi.net
Free Plan for 1250 monthly hits
150 Crypto and physical currencies - live updates
Base currency is set as USD on free account
Requires registration.
Documentation: currencyapi.net/documentation
JSON Response:
{
"valid": true,
"updated": 1567957373,
"base": "USD",
"rates": {
"AED": 3.673042,
"AFN": 77.529504,
"ALL": 109.410403,
// 165 currencies + some cryptos
}
}
Currency from LabStack
Website: https://labstack.com/currency
Documentation: https://labstack.com/docs/api/currency/convert
Pricing: https://labstack.com/pricing
Request: https://currency.labstack.com/api/v1/convert/1/USD/INR
Response:
```js
{
"time": "2019-10-09T21:15:00Z",
"amount": 71.1488
}
```
1: http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.xchange where pair in ("USDEUR", "USDJPY", "USDBGN", "USDCZK", "USDDKK", "USDGBP", "USDHUF", "USDLTL", "USDLVL", "USDPLN", "USDRON", "USDSEK", "USDCHF", "USDNOK", "USDHRK", "USDRUB", "USDTRY", "USDAUD", "USDBRL", "USDCAD", "USDCNY", "USDHKD", "USDIDR", "USDILS", "USDINR", "USDKRW", "USDMXN", "USDMYR", "USDNZD", "USDPHP", "USDSGD", "USDTHB", "USDZAR", "USDISK")&env=store://datatables.org/alltableswithkeys
currency-api
Free & Blazing Fast response using CDN
No Rate limits
150+ Currencies, Including Common Cryptocurrencies
Daily Updated
Documentation: Link
Request: https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/eur/jpy.json
Request(Fallback): https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/eur/jpy.min.json
Response:
{
"date": "2021-10-03",
"jpy": 128.798673
}
The European Central Bank (ECB) also has the most reliable free feed that I know of. It contains approx 28 currencies and is updated at least daily.
http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml
For more formats and tools see the ECB reference page:
http://www.ecb.int/stats/exchange/eurofxref/html/index.en.html
Yahoo has a YQL feature to get a whole bunch of currencies at once in XML or JSON. I've noticed the data is up to date by the minute where the ECB has day old data, and stops in the weekend.
http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.xchange where pair in ("USDEUR", "USDJPY", "USDBGN", "USDCZK", "USDDKK", "USDGBP", "USDHUF", "USDLTL", "USDLVL", "USDPLN", "USDRON", "USDSEK", "USDCHF", "USDNOK", "USDHRK", "USDRUB", "USDTRY", "USDAUD", "USDBRL", "USDCAD", "USDCNY", "USDHKD", "USDIDR", "USDILS", "USDINR", "USDKRW", "USDMXN", "USDMYR", "USDNZD", "USDPHP", "USDSGD", "USDTHB", "USDZAR", "USDISK")&env=store://datatables.org/alltableswithkeys
Here is their query builder, where you can test a query and copy the url:
http://developer.yahoo.com/yql/console/?q=show%20tables&env=store://datatables.org/alltableswithkeys#h=select%20*%20from%20yahoo.finance.xchange%20where%20pair%20in%20%28%22USDMXN%22%2C%20%22USDCHF%22%29
If you need a free and simple API for converting one currency to another, try free.currencyconverterapi.com.
Disclaimer, I'm the author of the website and I use it for one of my other websites.
The service is free to use even for commercial applications but offers no warranty. For performance reasons, the values are only updated every hour.
A sample conversion URL is: http://free.currencyconverterapi.com/api/v6/convert?q=EUR_PHP&compact=ultra&apiKey=sample-api-key which will return a json-formatted value, e.g. {"EUR_PHP":60.849184}
Here are some exchange APIs with PHP example.
[ Open Exchange Rates API ]
Provides 1,000 requests per month free. You must register and grab the App ID. The base currency USD for free account. Check the supported currencies and documentation.
// open exchange URL // valid app_id * REQUIRED *
$exchange_url = 'https://openexchangerates.org/api/latest.json';
$params = array(
'app_id' => 'YOUR_APP_ID'
);
// make cURL request // parse JSON
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $exchange_url . '?' . http_build_query($params),
CURLOPT_RETURNTRANSFER => true
));
$response = json_decode(curl_exec($curl));
curl_close($curl);
if (!empty($response->rates)) {
// convert 150 USD to JPY ( Japanese Yen )
echo $response->rates->JPY * 150;
}
150 USD = 18039.09015 JPY
[ Currency Layer API ]
Provides 1,000 requests per month free. You must register and grab the Access KEY. Custom base currency is not supported in free account. Check the documentation.
$exchange_url = 'http://apilayer.net/api/live';
$params = array(
'access_key' => 'YOUR_ACCESS_KEY',
'source' => 'USD',
'currencies' => 'JPY',
'format' => 1 // 1 = JSON
);
// make cURL request // parse JSON
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $exchange_url . '?' . http_build_query($params),
CURLOPT_RETURNTRANSFER => true
));
$response = json_decode(curl_exec($curl));
curl_close($curl);
if (!empty($response->quotes)) {
// convert 150 USD to JPY ( Japanese Yen )
echo '150 USD = ' . $response->quotes->USDJPY * 150 . ' JPY';
}
150 USD = 18036.75045 JPY
You can try geoplugin
Beside the geolocation done by IP (but the IP is the provider IP, so not so accurate), they return currencies also and have a currency converter: see examples.
They have 111 currencies updated.
If you're looking for a ruby based solution for this problem, I recommend using the Google Calculator method a solution similar to the following: http://j.mp/QIC564
require 'faraday'
require 'faraday_middleware'
require 'json'
# Debug:
# require "pry"
country_code_src = "USD"
country_code_dst = "INR"
connection = Faraday.get("http://www.google.com/ig/calculator?hl=en&q=1#{country_code_src}=?#{country_code_dst}")
currency_comparison_hash = eval connection.body #Google's output is not JSON, it's a hash
dst_currency_value, *dst_currency_text = *currency_comparison_hash[:rhs].split(' ')
dst_currency_value = dst_currency_value.to_f
dst_currency_text = dst_currency_text.join(' ')
puts "#{country_code_dst} -> #{dst_currency_value} (#{dst_currency_text} to 1 #{country_code_src})"
Here is one simple PHP Script which gets exchange rate between GBP and USD
<?php
$amount = urlencode("1");
$from_GBP0 = urlencode("GBP");
$to_usd= urlencode("USD");
$Dallor = "hl=en&q=$amount$from_GBP0%3D%3F$to_usd";
$US_Rate = file_get_contents("http://google.com/ig/calculator?".$Dallor);
$US_data = explode('"', $US_Rate);
$US_data = explode(' ', $US_data['3']);
$var_USD = $US_data['0'];
echo $to_usd;
echo $var_USD;
echo '<br/>';
?>
Google currency rates are not accurate google itself says ==> Google cannot guarantee the accuracy of the exchange rates used by the calculator. You should confirm current rates before making any transactions that could be affected by changes in the exchange rates. Foreign currency rates provided by Citibank N.A. are displayed under licence. Rates are for information purposes only and are subject to change without notice. Rates for actual transactions may vary and Citibank is not offering to enter into any transaction at any rate displayed.
For all newbie guys searching for some hint about currency conversion, take a look at this link. Datavoila
It helped med a lot regarding my own project in C#. Just in case the site disappears, I'll add the code below. Just add the below steps to your own project. Sorry about the formatting.
const string fromCurrency = "USD";
const string toCurrency = "EUR";
const double amount = 49.95;
// For other currency symbols see http://finance.yahoo.com/currency-converter/
// Clear the output editor //optional use, AFAIK
Output.Clear();
// Construct URL to query the Yahoo! Finance API
const string urlPattern = "http://finance.yahoo.com/d/quotes.csv?s={0}{1}=X&f=l1";
string url = String.Format(urlPattern, fromCurrency, toCurrency);
// Get response as string
string response = new WebClient().DownloadString(url);
// Convert string to number
double exchangeRate =
double.Parse(response, System.Globalization.CultureInfo.InvariantCulture);
// Output the result
Output.Text = String.Format("{0} {1} = {2} {3}",
amount, fromCurrency,
amount * exchangeRate, toCurrency);

Sinatra + omniauth + Android, advice sought

I'm developing a Sinatra app for which I'd like to use OmniAuth. So far, I have something similar to this for the web app:
http://codebiff.com/omniauth-with-sinatra
I'd like the web app to be usable via Android phones which would use an API, authenticating by means of a token. The development of an API seems to be covered nicely here:
Sinatra - API - Authentication
What is not clear is now I might arrange the login procedure. Presumably it would be along these lines:
User selects what service to use, e.g. Twitter, FaceBook &c., by means of an in-app button on the Android device.
The Android app opens a webview to log in to the web app.
A token is somehow created, stored in the web app's database, and returned to the Android app so that it can be stored and used for subsequent API requests.
I'm not very clear on how point 3 might be managed - does anyone have any suggestions?
As no-one seems to have any suggestions, here's what I've come up with so far. I don't think it's very good, though.
I've added an API key to the user model, which is created when the user is first authenticated:
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :uid, String
property :name, String
property :nickname, String
property :created_at, DateTime
property :api_key, String, :key => true
end
....
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]},
{ :uid => auth["uid"],
:nickname => auth["info"]["nickname"],
:name => auth["info"]["name"],
:api_key => SecureRandom.hex(20),
:created_at => Time.now })
session[:user_id] = user.id
session[:api_key] = user.api_key
flash[:info] = "Welcome, #{user.name}"
redirect "/success/#{user.id}/#{user.api_key}"
end
If the authorisation works then the api_key is supplied to the Android app, which will presumably store it on the device somewhere:
get '/success/:id/:api_key', :check => :valid_key? do
user = User.get(params[:id],params[:api_key])
if user.api_key == params[:api_key]
{'api_key' => user.api_key}.to_json
else
error 401
end
end
All API calls are protected as in the link in my original post:
register do
def check (name)
condition do
error 401 unless send(name) == true
end
end
end
helpers do
def valid_key?
user = User.first(:api_key => params[:api_key])
if !user.nil?
return true
end
return false
end
end
For public use I'll only allow SSL connections to the server. Any suggestions for improvement would be welcome.

Categories

Resources