private async Task InternalSaveTranscript(int resourceId, string transcript)
 {
     var entity = new GameCopycatEntity2
     {
         PartitionKey    = KeyUtils.IntToKey(resourceId),
         RowKey          = KeyUtils.DateTimeToDescKey(DateTime.UtcNow),
         Transcript      = transcript,
         UserId          = this.GetUserId(),
         UserDisplayName = this.GetUserDisplayName(),
     };
     await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.GameCopycat, entity);
 }
        public async Task <IHttpActionResult> PostResourceView([FromUri] int id, [FromBody] JObject value)
        {
            // A suggestion may have resourceId == null(0?) when the resource is made on the client in code and points to Google

            // TODO. Idea: Get the unauthenicated user's session cookie and log views for the session in a separate datastore.
            var userId = this.GetUserId(); // GetUserId() returns 0 for an unauthenticated user. That's fine. We log every view.

            var logEnity = new DynamicTableEntity(KeyUtils.GetCurrentTimeKey(), KeyUtils.IntToKey(userId));

            logEnity["Json"] = new EntityProperty(value.ToString());
            var logTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryLog, logEnity);

            if (userId != 0 && id != 0)
            {
                var viewTask = DapperHelper.ExecuteResilientlyAsync("dbo.libPostResourceView",
                                                                    new
                {
                    UserId     = userId,
                    ResourceId = id,
                },
                                                                    CommandType.StoredProcedure);

                // We use KeyUtils.LocalTimeToInvertedKey() to keep the local time and to order last records first for retrieval.
                var localTime = (string)value["localTime"];
                var rowKey    = KeyUtils.LocalTimeToInvertedKey(localTime);

                if (String.IsNullOrEmpty(rowKey))
                {
                    throw new ArgumentOutOfRangeException(localTime);
                }

                var historyEntity = new LibraryHistoryEntity
                {
                    PartitionKey = KeyUtils.IntToKey(userId),
                    RowKey       = rowKey,
                    ResourceId   = id,
                };
                var historyTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryHistory, historyEntity);

                // Do all the tasks in parallel.
                await Task.WhenAll(viewTask, historyTask, logTask);
            }
            else
            {
                await logTask;
            }

            return(StatusCode(HttpStatusCode.NoContent));
        }
Beispiel #3
0
        private async Task SendSmsMessage(string text, string tablePartitionKey)
        {
            // Send SMS
            var accountSid      = ConfigurationManager.AppSettings["Twilio.AccountSid"];
            var authToken       = ConfigurationManager.AppSettings["Twilio.AuthToken"];
            var fromPhoneNumber = ConfigurationManager.AppSettings["Twilio.PhoneNumber.SMS"];
            var twilio          = new TwilioRestClient(accountSid, authToken);
            var message         = twilio.SendMessage(fromPhoneNumber, "+16477711715", text);

            var entity = new ExternalSessionLogEntity
            {
                PartitionKey = tablePartitionKey,
                RowKey       = "TwilioMessage",
                Data         = JsonUtils.SerializeAsJson(new { Message = message, }),
            };
            await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity);
        }
Beispiel #4
0
        public static async Task <string> SaveMessageAndGetExtId(string message)
        {
            string extId = null;

            // We cannot use a transaction in heterogeneous storage mediums. An orphaned message on Azure Table is not a problem. The main message metadata is in the database anyway.
            if (!String.IsNullOrWhiteSpace(message))
            {
                extId = KeyUtils.GetTwelveBase32Digits();
                var entity = new UserMessageEntity
                {
                    PartitionKey = extId,
                    RowKey       = String.Empty,
                    Text         = message,
                };
                await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.UserMessages, entity);
            }
            return(extId);
        }
Beispiel #5
0
        public async Task <IHttpActionResult> InboundWebhook(InboundWebhookFormModel form)
        {
            // The form is empty on a HEAD request
            if (form != null)
            {
                var mandrill_events = form.mandrill_events;
                var partitionKey    = KeyUtils.GetCurrentTimeKey();

                var entity = new ExternalSessionLogEntity
                {
                    PartitionKey = partitionKey,
                    RowKey       = "InboundWebhook",
                    Data         = mandrill_events,
                };
                await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity);

                await SendSmsMessage("InboundWebhook " + partitionKey, partitionKey);
            }

            return(StatusCode(HttpStatusCode.NoContent));
        }
        public async Task <IHttpActionResult> GetCollection(string q)
        {
            if (String.IsNullOrWhiteSpace(q))
            {
                return(BadRequest("Empty query."));
            }
            var collectionName = GeneralUtils.SanitizeSpaceSeparatedWords(q);

            // Try to find the picture collection for the query in the internal Table storage.
            var    table     = AzureStorageUtils.GetCloudTable(AzureStorageUtils.TableNames.GamePicapick);
            var    operation = TableOperation.Retrieve <GamePickapicEntity>(collectionName, String.Empty);
            var    entity    = (await table.ExecuteAsync(operation)).Result as GamePickapicEntity;
            string json      = entity != null ? entity.Json : null;

            // If the data not found in our internal Table, query the YouTube API.
            if (json == null)
            {
                // We do not use the Google API Client library to materialize result as a POCO. Anyway the API itself is RESTful, and JSON can be parsed easily. Avoid overhead, overkill, bloatware etc.
                // +https://developers.google.com/apis-explorer/#p/youtube/v3/youtube.search.list

                var youtubeParams = HttpUtility.ParseQueryString(String.Empty); // returns System.Web.HttpValueCollection: System.Collections.Specialized.NameValueCollection
                youtubeParams["key"]        = ConfigurationManager.AppSettings["YoutubeApiKey"];
                youtubeParams["part"]       = "snippet";
                youtubeParams["type"]       = "video";
                youtubeParams["maxResults"] = "50";
                youtubeParams["q"]          = collectionName;
                youtubeParams["fields"]     = "items(id,snippet(thumbnails(high)))";
                var youtubeQueryString = youtubeParams.ToString();

                var url = "https://www.googleapis.com/youtube/v3/search?" + youtubeQueryString;

                var handler = new HttpClientHandler();
                if (handler.SupportsAutomaticDecompression)
                {
                    handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                }

                string response = null;
                using (var client = new HttpClient(handler))
                {
                    // If User-Agent is not sent, the server ignores "Accept-Encoding: gzip, deflate" and does not compress the response. The observed compression is 10kB -> 1kB.
                    client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "google-api-dotnet-client/1.8.1.31685 (gzip)");

                    response = await client.GetStringAsync(url);
                }

                var urls = JObject.Parse(response)
                           .GetValue("items")
                           .Children()
                           .Select(i => i["snippet"]["thumbnails"]["high"]["url"].Value <string>())
                           .OrderBy(i => Guid.NewGuid()) // Shuffle
                           .ToArray();

                var controlNumber = Math.Abs(String.Join(String.Empty, urls).GetHashCode()) % 100;
                json   = JsonUtils.SerializeAsJson(new { CollectionName = collectionName, ControlNumber = controlNumber, Urls = urls, });
                entity = new GamePickapicEntity(collectionName, json);
                await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.GamePicapick, entity);
            }

            return(new RawStringResult(this, json, RawStringResult.TextMediaType.Json));
        }
Beispiel #7
0
        public async Task <long?> BookSession(int courseId, DateTime start, string referer, string tablePartitionKey)
        {
            // Time slot is encoded in a weired way.
            var origin = DateTime.UtcNow.Date;
            var diff   = start - origin;
            var day    = Convert.ToInt32(Math.Floor(diff.TotalDays)) - 1; // Day number for the next day is 0.

            if (day < 0)
            {
                throw new Exception(ItalkiHelper.TimeSlotUnavailableError);
            }
            var halfHour = (diff.Hours * 2) + (diff.Minutes == 30 ? 1 : 0); // Convert.ToInt32(1.0 * diff.Minutes / 30); // We expect minutes to be either 00 or 30.
            var timeText = String.Format("{0},{1}", day, halfHour);

            // We will write to the log.
            var partitionKey0 = KeyUtils.GetCurrentTimeKey();

            // Pre-booking validation. It might be redundand. We may want to do it just in case.
            // Note that the validation call does not send the courseId. That means the server uses a session(???) or a cookie or the Referer header. It may cause problems in a multi-teacher scenario, until we use a separate HttpClient for each teacher.
            //var content1 = new FormUrlEncodedContent(new[]
            //{
            //    new KeyValuePair<string, string>("MethodName", "ValidSessionTime"),
            //    new KeyValuePair<string, string>("time", timeText),
            //});
            //var request1 = new HttpRequestMessage
            //{
            //    RequestUri = new Uri(ItalkiWebServicesUrl),
            //    Method = HttpMethod.Post,
            //    Content = content1,
            //};
            //request1.Headers.Add("Referer", referer);

            //var response1 = await httpClient.SendAsync(request1);

            //var statusCode1 = (int)response1.StatusCode;
            //string responseContent1 = null;
            //if (response1.IsSuccessStatusCode)
            //{
            //    responseContent1 = await response1.Content.ReadAsStringAsync();
            //}

            //var entity1 = new ExternalSessionLogEntity
            //{
            //    PartitionKey = partitionKey,
            //    RowKey = "ValidationResponse",
            //    Data = responseContent1,
            //    HttpStatus = statusCode1,
            //};
            //await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity1);

            //response1.EnsureSuccessStatusCode();

            // Send the booking request
#if DEBUG
            var password = "******";
#else
            var password = ConfigurationManager.AppSettings["Italki.Password1"];
#endif
            var content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair <string, string>("MethodName", "RequestSession"),
                new KeyValuePair <string, string>("time", timeText), // It will be Url-encoded by the form oject.
                new KeyValuePair <string, string>("tool", "1"),
                new KeyValuePair <string, string>("sim", "andrey.fomin3"),
                new KeyValuePair <string, string>("msg", ":)"), // ":-)  :)  :D  :o)  :]  :3  :c)  :>  =]  8)  =)  :}  :^)"
                new KeyValuePair <string, string>("pwd", password),
                new KeyValuePair <string, string>("cid", courseId.ToString()),
            });

            var request = new HttpRequestMessage
            {
                RequestUri = new Uri(ItalkiWebServicesUrl),
                Method     = HttpMethod.Post,
                Content    = content,
            };
            request.Headers.Add("Referer", referer);

            // Write to the log
            var entity = new ExternalSessionLogEntity
            {
                PartitionKey = tablePartitionKey,
                RowKey       = "ItalkiRequest",
                Data         = JsonUtils.SerializeAsJson(new
                {
                    CourseId = courseId,
                    Start    = start,
                    Time     = timeText,
                }),
            };
            await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity);

            // Book the session with Italki
            var response = await httpClient.SendAsync(request);

            var    statusCode      = (int)response.StatusCode;
            string responseContent = null;
            if (response.IsSuccessStatusCode)
            {
                responseContent = await response.Content.ReadAsStringAsync();
            }

            // Write the response to the log
            entity = new ExternalSessionLogEntity
            {
                PartitionKey = tablePartitionKey,
                RowKey       = "ItalkiResponse",
                Data         = responseContent,
                HttpStatus   = statusCode,
            };
            await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity);

            response.EnsureSuccessStatusCode();

            long?extSessionId = null;
            try
            {
                var items = JArray.Parse(responseContent);

                /* +https://secure.italki.com/sessions/schedule_20131106.js?v=150302 , Line 853
                 * if(result[0]=="-2") $get("MsgPrompt").innerHTML=TS312;
                 * if(result[0]=="-1") $get("MsgPrompt").innerHTML=TS313;
                 * if(result[0]=="1") $get("MsgPrompt").innerHTML=TS314;
                 * if(result[0]=="-3") $get("MsgPrompt").innerHTML=TS315;
                 * In HTML
                 * var TS312="The time you\'re scheduling was taken by other students. Please try other time.";
                 * var TS313="Your payment account balance is not enough to pay for the session(s) you are scheduling.";
                 * var TS314="Trial session is not available.";
                 * var TS315="Incorrect password";
                 */
                var result = (string)items.FirstOrDefault();
                if ((result == "Y") || (result == "Y1"))
                {
                    var idArr = items[3];
                    if (idArr is JArray)
                    {
                        extSessionId = (long)idArr.First();
                    }
                }
            }
            // We may get an HTML instead of JSON in response.
            catch (JsonReaderException)
            {
            }

            return(extSessionId);
        }