Closed8

Azure Functions と OpenAI API を使って Line 返答サービスを作ってみる

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

それっぽいコード
まずはFunction本体。System.Text.Jsonで実装してみたやつ。

private static readonly string _messagingApiUrl = "https://api.line.me/v2/bot/message/reply";
private static readonly string _accessToken = "";
private static readonly string _channelSecret = "";
private static readonly string _apiKey = "";
private static HttpClient _httpClient = new HttpClient();

[FunctionName("LineReply")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    req.Headers.TryGetValue("X-Line-Signature", out var xLineSignature);

    log.LogInformation($"Body : {requestBody}");

    var json = System.Text.Json.JsonSerializer.Deserialize<LineMessageReceiveJson>(requestBody);

    log.LogInformation($"Message: {json.events[0].message.text}");
    if (IsSingature(xLineSignature, requestBody, _channelSecret))
    {
        if (json.events[0].type == "message")
        {
            var api = new OpenAIAPI(_apiKey);
            var chat = api.Chat.CreateConversation();
            var prompt = json.events[0].message.text;
            chat.AppendUserInput(prompt);
            var result = await chat.GetResponseFromChatbotAsync();
            // ChatGPTから返答
            await SendTextReplyAsync(json.events[0].replyToken, result);
            return new OkResult();
        }
    }

    return new BadRequestResult();
}

private static async Task SendTextReplyAsync(string replyToken, string message)
{
    _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);

    var response = await _httpClient.PostAsJsonAsync<LineTextReplyJson>(_messagingApiUrl, new LineTextReplyJson()
    {
        replyToken = replyToken,
        messages = new List<Message>()
                {
                    new Message(){
                        type = "text",
                        text = message
                    }
                }
    });
    response.EnsureSuccessStatusCode();
}

private static bool IsSingature(string signature, string text, string key)
{
    var textBytes = Encoding.UTF8.GetBytes(text);
    var keyBytes = Encoding.UTF8.GetBytes(key);

    using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
    {
        var hash = hmac.ComputeHash(textBytes, 0, textBytes.Length);
        var hash64 = Convert.ToBase64String(hash);

        return signature == hash64;
    }
}
たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

あとはJsonのプロパティ

受信用

public class LineMessageReceiveJson
{
    public string destination { get; set; }
    public List<Event> events { get; set; }
}

public class Event
{
    public string replyToken { get; set; }
    public string type { get; set; }
    public object timestamp { get; set; }
    public Source source { get; set; }
    public Message message { get; set; }
}

public class Message
{
    public string id { get; set; }
    public string type { get; set; }
    public string text { get; set; }
}

public class Source
{
    public string type { get; set; }
    public string userId { get; set; }
}

返信用(Messageクラスは使いまわし)

public class LineTextReplyJson
    {
        public string replyToken { get; set; }
        public List<Message> messages { get; set; }
        public bool notificationDisabled { get; set; }
    }
たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

IsSingature の判定がないと返信してくれないみたいなので、動かないなぁと思ったらこれの実装忘れを確認したほうがよさそう。

あとは「関数のURLの取得」からリンクを発行して、Webhookに設定。Webhookの利用をオンにすることをわすれずに。

このスクラップは2023/04/22にクローズされました