Skip to content

Commit

Permalink
Merge pull request #1412 from TelegramBots/develop
Browse files Browse the repository at this point in the history
AuthHelpers.ParseValidateData, AnswerPreCheckoutQueryAsync and other stuff
  • Loading branch information
wiz0u committed Aug 23, 2024
2 parents 98e4aee + f0c1b7d commit 24dd537
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 39 deletions.
49 changes: 49 additions & 0 deletions .azure-pipelines/azure-nuget-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
trigger:
batch: true
branches:
include:
- master
paths:
exclude:
- '.github'
- docs
- README.md
- CHANGELOG.md
- CONTRIBUTING.md

pr: none

variables:
- template: variables.yml

pool:
vmImage: $(vmImage)

steps:
- task: UseDotNet@2
displayName: Install .NET sdk
inputs:
packageType: sdk
version: $(netSdkVersion)
- pwsh: >
dotnet build
--configuration $(buildConfiguration)
-p:Version=$(releaseVersion)
$(projectPath)
displayName: Build project with release version
- pwsh: >
dotnet pack
--no-build
--output "$(Build.ArtifactStagingDirectory)/packages"
--configuration $(buildConfiguration)
-p:Version=$(releaseVersion)
$(projectPath)
displayName: Create nuget package
- task: NuGetAuthenticate@1
- pwsh: >
dotnet nuget push
$(Build.ArtifactStagingDirectory)/packages/*.nupkg
--skip-duplicate
--source https://pkgs.dev.azure.com/tgbots/Telegram.Bot/_packaging/release/nuget/v3/index.json
--api-key AZ
displayName: Publish package to NuGet
4 changes: 2 additions & 2 deletions .azure-pipelines/variables.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
variables:
variables:
- group: Integration Tests Variables
- name: versionPrefix
value: 21.10.0
value: 21.10.1
- name: versionSuffix
value: ''
- name: ciVersionSuffix
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# .NET Client for Telegram Bot API

[![Nuget](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fnuget.voids.site%2Fv3%2Fpackage%2FTelegram.Bot%2Findex.json&query=versions%5B-1%3A%5D&style=flat-square&label=Telegram.Bot&color=d8b541)](https://nuget.voids.site/packages/Telegram.Bot)
[![Nuget](https://img.shields.io/badge/dynamic/json?url=https://pkgs.dev.azure.com/tgbots/Telegram.Bot/_packaging/release/nuget/v3/flat2/Telegram.Bot/index.json&query=versions%5B-1%3A%5D&style=flat-square&label=Telegram.Bot&color=d8b541)](https://dev.azure.com/tgbots/Telegram.Bot/_artifacts/feed/release/NuGet/Telegram.Bot)
[![Bot API Version](https://img.shields.io/badge/Bot_API-7.9-f36caf.svg?style=flat-square)](https://core.telegram.org/bots/api)
[![Documentations](https://img.shields.io/badge/Documentations-Book-orange.svg?style=flat-square)](https://telegrambots.github.io/book/)
[![Telegram Chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://t.me/joinchat/B35YY0QbLfd034CFnvCtCA)
Expand All @@ -23,7 +23,7 @@ We, the [Telegram Bots team], mainly focus on developing multiple [NuGet package
|Packages|Documentation|News Channel|Team|Group Chat|
|:-----:|:-----------:|:----------:|:--:|:--------:|
| [![Packages](docs/logo-nuget.png)](https://nuget.voids.site/packages/Telegram.Bot) | [![documentations](docs/logo-docs.png)](https://telegrambots.github.io/book/) | [![News Channel](docs/logo-channel.jpg)](https://t.me/s/tgbots_dotnet) | [![Team](docs/logo-gh.png)](https://github.com/orgs/TelegramBots/people) | [![Group Chat](docs/logo-chat.jpg)](https://t.me/joinchat/B35YY0QbLfd034CFnvCtCA) |
| [![Packages](docs/logo-nuget.png)](https://dev.azure.com/tgbots/Telegram.Bot/_artifacts/feed/release/NuGet/Telegram.Bot) | [![documentations](docs/logo-docs.png)](https://telegrambots.github.io/book/) | [![News Channel](docs/logo-channel.jpg)](https://t.me/s/tgbots_dotnet) | [![Team](docs/logo-gh.png)](https://github.com/orgs/TelegramBots/people) | [![Group Chat](docs/logo-chat.jpg)](https://t.me/joinchat/B35YY0QbLfd034CFnvCtCA) |
| Our nuget package feed | Telegram bots book | Subscribe to 📣 [`@tgbots_dotnet`] channel to get our latest news | The team contributing to this work | [Join our chat] 💬 to talk about bots and ask questions |

## 🔨 Getting Started
Expand Down
9 changes: 9 additions & 0 deletions src/Telegram.Bot/Extend.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ public partial class BotCommandScope
public static BotCommandScopeChatMember ChatMember(ChatId chatId, long userId) => new() { ChatId = chatId, UserId = userId };
}

namespace Payments
{
public partial class LabeledPrice
{
/// <summary>Instantiates a new <see cref="LabeledPrice"/></summary>
public static implicit operator LabeledPrice((string label, int amount) t) => new(t.label, t.amount);
}
}

namespace ReplyMarkups
{
public partial class ReplyKeyboardMarkup
Expand Down
51 changes: 51 additions & 0 deletions src/Telegram.Bot/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#if NET6_0_OR_GREATER
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.WebUtilities;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
#endif
Expand Down Expand Up @@ -87,3 +91,50 @@ public sealed override async Task WriteResponseBodyAsync(OutputFormatterWriteCon
#endif
}
}

#if NET6_0_OR_GREATER
namespace Telegram.Bot
{
/// <summary>Provides methods to authenticate data coming from Telegram web requests</summary>
public static class AuthHelpers
{
#pragma warning disable MA0002, MA0006
/// <summary>Used to parse and validate <see href="https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app">Telegram.WebApp.initData</see> or <see href="https://core.telegram.org/widgets/login#receiving-authorization-data">LoginWidget requests</see></summary>
/// <param name="initData">Data in the form of a <see href="https://en.wikipedia.org/wiki/Query_string">query string</see>, passed to your web app</param>
/// <param name="botToken">The bot token</param>
/// <param name="loginWidget">true to validate a LoginWidget request, false to validate a WebApp.initData</param>
/// <returns>On success, data fields in a sorted dictionary (without the security hash)</returns>
/// <exception cref="SecurityException">Authentication failed</exception>
public static SortedDictionary<string, string> ParseValidateData(string? initData, string botToken, bool loginWidget = false)
{
var query = QueryHelpers.ParseQuery(initData);
return ParseValidateData(query.ToDictionary(kvp => kvp.Key, kvp => (string?)kvp.Value ?? ""), botToken, loginWidget);
}

/// <summary>Used to parse and validate <see href="https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app">Telegram.WebApp.initData</see> or <see href="https://core.telegram.org/widgets/login#receiving-authorization-data">LoginWidget requests</see></summary>
/// <param name="fields">Data fields, possibly obtained from the <see href="https://core.telegram.org/widgets/login#receiving-authorization-data">data-onauth</see> function</param>
/// <param name="botToken">The bot token</param>
/// <param name="loginWidget">true to validate a LoginWidget request, false to validate a WebApp.initData</param>
/// <returns>On success, data fields in a sorted dictionary (without the security hash)</returns>
/// <exception cref="SecurityException">Authentication failed</exception>
public static SortedDictionary<string, string> ParseValidateData(IDictionary<string, string> fields, string botToken, bool loginWidget = false)
=> ParseValidateData(new SortedDictionary<string, string>(fields), botToken, loginWidget);

private static SortedDictionary<string, string> ParseValidateData(SortedDictionary<string, string> fields, string botToken, bool loginWidget = false)
{
if (fields.Remove("hash", out var hash))
{
var dataCheckString = string.Join('\n', fields.Select(kvp => $"{kvp.Key}={kvp.Value}"));
var secretKey = loginWidget
? SHA256.HashData(Encoding.ASCII.GetBytes(botToken))
: HMACSHA256.HashData(Encoding.ASCII.GetBytes("WebAppData"), Encoding.ASCII.GetBytes(botToken));
var computedHash = HMACSHA256.HashData(secretKey, Encoding.UTF8.GetBytes(dataCheckString));
if (computedHash.SequenceEqual(Convert.FromHexString(hash)))
return fields;
}
throw new SecurityException("Invalid data hash");
}
}
#pragma warning restore MA0002 // IEqualityComparer<string> or IComparer<string> is missing
}
#endif
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Telegram.Bot.Requests;

/// <summary>Use this method to create a <a href="https://telegram.org/blog/superchannels-star-reactions-subscriptions#star-subscriptions">subscription invite link</a> for a channel chat. The bot must have the <em>CanInviteUsers</em> administrator rights. The link can be edited using the method <see cref="TelegramBotClientExtensions.EditChatSubscriptionInviteLinkAsync">EditChatSubscriptionInviteLink</see> or revoked using the method <see cref="TelegramBotClientExtensions.RevokeChatInviteLinkAsync">RevokeChatInviteLink</see>.<para>Returns: The new invite link as a <see cref="ChatInviteLink"/> object.</para></summary>
/// <summary>Use this method to create a <a href="https://telegram.org/blog/superchannels-star-reactions-subscriptions#star-subscriptions">subscription invite link</a> for a channel chat. The bot must have the <em>CanInviteUsers</em> administrator rights. The link can be edited using the method <see cref="TelegramBotClientExtensions.EditChatSubscriptionInviteLink">EditChatSubscriptionInviteLink</see> or revoked using the method <see cref="TelegramBotClientExtensions.RevokeChatInviteLinkAsync">RevokeChatInviteLink</see>.<para>Returns: The new invite link as a <see cref="ChatInviteLink"/> object.</para></summary>
public partial class CreateChatSubscriptionInviteLinkRequest : RequestBase<ChatInviteLink>, IChatTargetable
{
/// <summary>Unique identifier for the target channel chat or username of the target channel (in the format <c>@channelusername</c>)</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ public partial class AnswerPreCheckoutQueryRequest : RequestBase<bool>
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public required string PreCheckoutQueryId { get; set; }

/// <summary>Specify <see langword="true"/> if everything is alright at this stage and the bot is ready to proceed.<br/> Use <see langword="false"/> and fill <see cref="ErrorMessage"/> if there are any problems.</summary>
/// <summary>Specify <see langword="true"/> if everything is alright (goods are available, etc.) and the bot is ready to proceed with the order. Use <see langword="false"/> if there are any problems.</summary>
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public required bool Ok { get; set; }

/// <summary>Error message in human readable form that explains the reason for failure to proceed with the checkout (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user.</summary>
/// <summary>Error message in human readable form that explains the reason for failure to proceed with the checkout (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user.<para/>Leave <see langword="null"/> if everything is alright (goods are available, etc.) and the bot is ready to proceed with the order</summary>
public string? ErrorMessage { get; set; }

/// <summary>Instantiates a new <see cref="AnswerPreCheckoutQueryRequest"/></summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public partial class AnswerShippingQueryRequest : RequestBase<bool>
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public required string ShippingQueryId { get; set; }

/// <summary>Specify <see langword="true"/> if everything is alright at this stage and the bot is ready to proceed.<br/> Use <see langword="false"/> and fill <see cref="ErrorMessage"/> if there are any problems.</summary>
/// <summary>Pass <see langword="true"/> if delivery to the specified address is possible and <see langword="false"/> if there are any problems (for example, if delivery to the specified address is not possible)</summary>
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public required bool Ok { get; set; }

Expand Down
10 changes: 1 addition & 9 deletions src/Telegram.Bot/Serialization/UnixDateTimeConverterUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@ namespace Telegram.Bot.Serialization;

internal static class UnixDateTimeConverterUtil
{
private static readonly DateTime UnixEpoch = new(
year: 1970,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
kind: DateTimeKind.Utc
);
private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

internal static DateTime Read(ref Utf8JsonReader reader, Type typeToConvert)
{
Expand Down
22 changes: 4 additions & 18 deletions src/Telegram.Bot/TelegramBotClientExtensions.ApiMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,7 @@ public static async Task<ChatInviteLink> EditChatInviteLinkAsync(
CreatesJoinRequest = createsJoinRequest,
}, cancellationToken).ConfigureAwait(false);

/// <summary>Use this method to create a <a href="https://telegram.org/blog/superchannels-star-reactions-subscriptions#star-subscriptions">subscription invite link</a> for a channel chat. The bot must have the <em>CanInviteUsers</em> administrator rights. The link can be edited using the method <see cref="TelegramBotClientExtensions.EditChatSubscriptionInviteLinkAsync">EditChatSubscriptionInviteLink</see> or revoked using the method <see cref="TelegramBotClientExtensions.RevokeChatInviteLinkAsync">RevokeChatInviteLink</see>.</summary>
/// <summary>Use this method to create a <a href="https://telegram.org/blog/superchannels-star-reactions-subscriptions#star-subscriptions">subscription invite link</a> for a channel chat. The bot must have the <em>CanInviteUsers</em> administrator rights. The link can be edited using the method <see cref="TelegramBotClientExtensions.EditChatSubscriptionInviteLink">EditChatSubscriptionInviteLink</see> or revoked using the method <see cref="TelegramBotClientExtensions.RevokeChatInviteLinkAsync">RevokeChatInviteLink</see>.</summary>
/// <param name="botClient">An instance of <see cref="ITelegramBotClient"/></param>
/// <param name="chatId">Unique identifier for the target channel chat or username of the target channel (in the format <c>@channelusername</c>)</param>
/// <param name="subscriptionPeriod">The number of seconds the subscription will be active for before the next payment. Currently, it must always be 2592000 (30 days).</param>
Expand Down Expand Up @@ -3145,31 +3145,17 @@ public static async Task AnswerShippingQueryAsync(
/// <summary>Once the user has confirmed their payment and shipping details, the Bot API sends the final confirmation in the form of an <see cref="Update"/> with the field <em>PreCheckoutQuery</em>. Use this method to respond to such pre-checkout queries <b>Note:</b> The Bot API must receive an answer within 10 seconds after the pre-checkout query was sent.</summary>
/// <param name="botClient">An instance of <see cref="ITelegramBotClient"/></param>
/// <param name="preCheckoutQueryId">Unique identifier for the query to be answered</param>
/// <param name="errorMessage">Error message in human readable form that explains the reason for failure to proceed with the checkout (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user.<para/>Leave <see langword="null"/> if everything is alright (goods are available, etc.) and the bot is ready to proceed with the order</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
public static async Task AnswerPreCheckoutQueryAsync(
this ITelegramBotClient botClient,
string preCheckoutQueryId,
string? errorMessage = default,
CancellationToken cancellationToken = default
) => await botClient.ThrowIfNull().MakeRequestAsync(new AnswerPreCheckoutQueryRequest
{
PreCheckoutQueryId = preCheckoutQueryId,
Ok = true,
}, cancellationToken).ConfigureAwait(false);

/// <summary>Once the user has confirmed their payment and shipping details, the Bot API sends the final confirmation in the form of an <see cref="Update"/> with the field <em>PreCheckoutQuery</em>. Use this method to respond to such pre-checkout queries <b>Note:</b> The Bot API must receive an answer within 10 seconds after the pre-checkout query was sent.</summary>
/// <param name="botClient">An instance of <see cref="ITelegramBotClient"/></param>
/// <param name="preCheckoutQueryId">Unique identifier for the query to be answered</param>
/// <param name="errorMessage">Error message in human readable form that explains the reason for failure to proceed with the checkout (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
public static async Task AnswerPreCheckoutQueryAsync(
this ITelegramBotClient botClient,
string preCheckoutQueryId,
string errorMessage,
CancellationToken cancellationToken = default
) => await botClient.ThrowIfNull().MakeRequestAsync(new AnswerPreCheckoutQueryRequest
{
PreCheckoutQueryId = preCheckoutQueryId,
Ok = false,
Ok = errorMessage == null,
ErrorMessage = errorMessage,
}, cancellationToken).ConfigureAwait(false);

Expand Down
10 changes: 7 additions & 3 deletions test/Telegram.Bot.Tests.Integ/Payments/PaymentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ await paymentsBuilder.MakeShippingQueryRequest(Fixture,
Assert.NotNull(shippingUpdate.ShippingQuery.ShippingAddress.PostCode);
}

[OrderedFact("Should send invoice for no shipment option, and reply pre-checkout query with OK.")]
[OrderedFact("Should send invoice with 2 shipment options, and reply pre-checkout query with OK.")]
[Trait(Constants.MethodTraitName, Constants.TelegramBotApiMethods.SendInvoice)]
[Trait(Constants.MethodTraitName, Constants.TelegramBotApiMethods.AnswerPreCheckoutQuery)]
public async Task Should_Answer_PreCheckout_Query_With_Ok_And_Shipment_Option()
Expand All @@ -132,8 +132,12 @@ public async Task Should_Answer_PreCheckout_Query_With_Ok_And_Shipment_Option()
.WithShipping(s => s
.WithTitle(title: "DHL Express")
.WithId(id: "dhl-express")
.WithPrice(label: "Packaging", amount: 400_000)
.WithPrice(label: "Shipping price", amount: 337_600))
.WithShipping(s => s
.WithTitle(title: "FedEx")
.WithId(id: "fedex")
.WithPrice(label: "Packaging", amount: 200_000)
.WithPrice(label: "Shipping price", amount: 237_600))
.WithCurrency("USD")
.WithPayload("<my-payload>")
.WithFlexible()
Expand All @@ -146,7 +150,7 @@ public async Task Should_Answer_PreCheckout_Query_With_Ok_And_Shipment_Option()
.CurrencyFormat();

string instruction = FormatInstructionWithCurrency(
$"Click on *Pay {totalCostWithoutShippingCost:C}* and send your shipping address. Then click *Pay {totalCostWithoutShippingCost:C}* inside payment dialog. Transaction should be completed."
$"Click on *Pay {totalCostWithoutShippingCost:C}* and send your shipping address. Then click *Pay {totalCostWithoutShippingCost:C}* inside payment dialog using DHL option. Transaction should be completed."
);
await Fixture.SendTestInstructionsAsync(instruction, chatId: classFixture.PrivateChat.Id);

Expand Down
2 changes: 1 addition & 1 deletion test/Telegram.Bot.Tests.Integ/Payments/PaymentsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public PreliminaryInvoice GetPreliminaryInvoice()

public int GetTotalAmount() =>
(_product?.ProductPrices.Sum(price => price.Amount) ?? 0) +
_shippingOptions.Sum(x => x.Prices.Sum(p => p.Amount));
_shippingOptions.Take(1).Sum(x => x.Prices.Sum(p => p.Amount));

public int GetTotalAmountWithoutShippingCost() => _product?.ProductPrices.Sum(price => price.Amount) ?? 0;

Expand Down

0 comments on commit 24dd537

Please sign in to comment.