V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tbxark
V2EX  ›  OpenAI

免费快速部署自己的 ChatGPT Telegram 机器人

  •  3
     
  •   tbxark ·
    tbxark · 97 天前 · 9391 次点击
    这是一个创建于 97 天前的主题,其中的信息可能已经有所发展或是发生改变。

    上班摸鱼随手写了个小脚本,最简单快捷部署属于自己的 ChatGPT Telegram 机器人的方法,单文件,直接复制粘贴一把梭,无需任何依赖,无需配置本地开发环境,不用域名,免服务器。

    https://github.com/TBXark/ChatGPT-Telegram-Workers

    https://github.com/TBXark/ChatGPT-Telegram-Workers/raw/master/demo.jpg

    配置

    偷懒的话可以直接复制代码然后修改相应的 KEY ,想要动态变化可以设置成环境变量

    // 你也可以注释这两行代码,然后在 Workers 配置界面填写环境变量
    const API_KEY = "PLEASE_REPLACE_WITH_YOUR_OPENAI_API_KEY";
    const TELEGRAM_TOKEN = "PLEASE_REPLACE_WITH_YOUR_TELEGRAM_BOT_TOKEN";
    
    // 聊天白名单,你也可以在环境变量中定义数组然后在这里解析 JSON
    const CHAT_WHITE_LIST = [];
    

    使用

    1. 新建 Telegram 机器人, 获得 Token
    2. 注册 OpenAI 账号并创建 API Key
    3. 部署 Workers https://developers.cloudflare.com/workers/
    4. 运行 https://your_workers_name.your_workers_subdomain.workers.dev/telegram/your_telegram_bot_token/bind 绑定 telegram
    5. 开始新对话 使用/new指令开始,之后每次都会将聊天上下文发送到 ChatGPT
    第 1 条附言  ·  96 天前
    • 修复域名BUG, 得手动输入一下自己的workers域名
    // 你在这代码直接添加信息,或者Workers配置界面填写环境变量, 环境变量的优先级比较高
    // OpenAI API Key
    let API_KEY = "PLEASE_REPLACE_WITH_YOUR_OPENAI_API_KEY";
    // Telegram Bot Token
    let TELEGRAM_TOKEN = "PLEASE_REPLACE_WITH_YOUR_TELEGRAM_BOT_TOKEN";
    // Workers Domain
    let YOUR_WORKERS_DOMAIN="your_workers_name.your_workers_subdomain.workers.dev"
    // Chat White List
    let CHAT_WHITE_LIST = [];
    
    第 2 条附言  ·  96 天前

    更新了一下配置步骤

    1. 新建Telegram机器人, 获得Token
    2. 注册OpenAI账号并创建API Key
    3. 部署Workers
    4. 配置环境变量(Settings-Variables-Environment Variables),设置API_KEYTELEGRAM_TOKEN,WORKERS_DOMAIN,CHAT_WHITE_LIST
    5. 绑定KV数据(Settings-Variables-KV Namespace Bindings),名字设置为DATABASE
    6. 运行 https://your_workers_name.your_workers_subdomain.workers.dev/init 绑定telegram
    7. 开始新对话,使用/new指令开始,之后每次都会将聊天上下文发送到ChatGPT
    第 3 条附言  ·  96 天前

    又用ChatGPT完善了一下使用说明

    使用

    1. 新建Telegram机器人, 获得Token
      1. 打开Telegram并向 BotFather 发送 /start 命令
      2. 发送 /newbot 命令,并给你的机器人起一个名字
      3. 给你的机器人取一个唯一的用户名
      4. BotFather 会生成一个 Token,复制下来保存好,这个 Token 是和你的机器人绑定的密钥,不要泄露给他人!
      5. 稍后再Cloudflare Workers 的设置里 将这个 Token 填入 TELEGRAM_TOKEN 变量中
      6. 在Telegram中找到BotFather, 发送/setcommands, 找到自己的机器人, 发送new - 开始新对话
    2. 注册OpenAI账号并创建API Key
      1. 打开 OpenAI 注册账号
      2. 点击右上角的头像,进入个人设置页面
      3. 点击 API Keys,创建一个新的 API Key
      4. 稍后再Cloudflare Workers 的设置里 将这个 Token 填入 API_KEY 变量中
    3. 部署Workers
      1. 打开 Cloudflare Workers 注册账号
      2. 点击右上角的 Create a Worker
      3. 将代码复制到编辑器中,保存
    4. 配置环境变量(Settings-Variables-Environment Variables),设置API_KEYTELEGRAM_TOKEN,WORKERS_DOMAIN,CHAT_WHITE_LIST
      1. 打开 Cloudflare Workers 点击你的Workers,点击右上角的 Setting -> Variables
      2. API_KEY:设置成 OpenAI API Key
      3. TELEGRAM_TOKEN:设置成 Telegram Bot Token
      4. WORKERS_DOMAIN:设置成你的Workers域名,例如your_workers_name.your_workers_subdomain.workers.dev
      5. CHAT_WHITE_LIST:设置成你想要使用机器人的聊天ID,例如123456789,987654321,可以在Telegram中使用/new指令获取
    5. 绑定KV数据(Settings-Variables-KV Namespace Bindings),名字设置为DATABASE
      1. 点击右上角的 Create a Namespace
      2. 设置名字为DATABASE
      3. 打开 Cloudflare Workers 点击你的Workers
      4. 点击右上角的 Setting -> Variables
      5. 在 KV Namespace Bindings 中点击 Edit variables
      6. 点击 Add variable
      7. 设置名字为DATABASE 并选择刚刚创建的KV数据
    6. 运行 https://your_workers_name.your_workers_subdomain.workers.dev/init 绑定telegram
    7. 开始新对话,使用/new指令开始,之后每次都会将聊天上下文发送到ChatGPT
    第 4 条附言  ·  96 天前

    上班随手写的脚本,没想到这么多人关注,回到家重构了一下代码,添加了留言里提到的一些需求,修复了很多BUG。基本消除了telegram死亡回调的问题

    用户配置

    每个用户的自定义配置,只能通过Telegram发送消息来修改,消息格式为SETENV KEY=VALUE |KEY|说明|例子| |--|--|--| |SYSTEM_INIT_MESSAGE|系统初始化参数,设定后就算开启新会话还能保持,不用每次都调试|SETENV SYSTEM_INIT_MESSAGE=现在开始是喵娘,每句话已喵结尾| |OPENAI_API_EXTRA_PARAMS|OpenAI API额外参数,设定后每次调用API都会带上,可以用来调整温度等参数|SETENV OPENAI_API_EXTRA_PARAMS={"temperature": 0.5}, 每次修改必须为完整JSON|

    116 条回复    2023-03-22 18:20:28 +08:00
    1  2  
    jihu9001
        1
    jihu9001  
       96 天前 via iPhone
    试了一下,机器人没有反应,好尴尬
    tbxark
        2
    tbxark  
    OP
       96 天前
    @jihu9001 更新一下代码,再试一下,刚才有 BUG
    Lentin
        3
    Lentin  
       96 天前
    这一段是做什么的 看起来像是一个 webhook 接口 是不是需要改一下呢?
    https://github.com/TBXark/ChatGPT-Telegram-Workers/blob/master/index.js#LL50C15-L50C39
    tbxark
        4
    tbxark  
    OP
       96 天前
    @jihu9001 你得先调用绑定才能有反应,可以先用 /new 获取自己的 id 然后添加到白名单
    tbxark
        5
    tbxark  
    OP
       96 天前
    @Lentin 啊对,要改成自己的域名
    jihu9001
        6
    jihu9001  
       96 天前 via iPhone
    CHAT_WHITE_LIST 这个列表元素是数字还是字符串?
    tbxark
        7
    tbxark  
    OP
       96 天前
    @jihu9001 都可以,判断白名单的时候统一转成字符串了,
    PS: 刚才更新了一下代码,你可以重新粘贴一下,建议变量都写在环境变量配置里面,这样更新代码的时候不影响原来配置比较方便。如果在环境变量里写 ID ,直接写用逗号分隔就可以了
    Lentin
        8
    Lentin  
       96 天前
    DATABASE 这个变量看起来好像还是有问题 发不了消息
    "message": "DATABASE.delete is not a function",
    "message": "Cannot read properties of null (reading 'delete')",
    arfaWong
        9
    arfaWong  
       96 天前   ❤️ 1
    @Lentin 创建一个 KV Namespace ,然后在 worker settings 里面绑定。绑定的 Variable name 要和作者代码里的 Database 一样。刚刚也是搞了很久 笑 cry
    tbxark
        10
    tbxark  
    OP
       96 天前
    tbxark
        12
    tbxark  
    OP
       96 天前
    @Lentin 😂 Sorry
    jihu9001
        13
    jihu9001  
       96 天前 via iPhone
    @Lentin 终于跑起来了,过程过于艰难,建议把 readme 写详细点,老哥这个项目还是非常稳的
    Lentin
        14
    Lentin  
       96 天前
    这里的空格 ,以及后面的引号,虽然不影响运行=3=
    https://github.com/TBXark/ChatGPT-Telegram-Workers/blob/master/index.js#LL7C19-L7C21
    tbxark
        15
    tbxark  
    OP
       96 天前
    @jihu9001 用 ChatGPT 完善了一下
    Lentin
        16
    Lentin  
       96 天前
    https://github.com/TBXark/ChatGPT-Telegram-Workers/blob/master/index.js#L108
    108 行加入以下代码可以使 chatgpt 的与 TG 自带的 markdown 兼容
    parse_mode: 'Markdown'
    tbxark
        17
    tbxark  
    OP
       96 天前
    @Lentin 确实是可以,之前试了一下没有反应就没加了,我调试一下看看
    tbxark
        18
    tbxark  
    OP
       96 天前
    @Lentin 😂 原来深色模式看不出来代码的 markdown ,我以为没有作用
    Lentin
        19
    Lentin  
       96 天前 via iPhone
    @tbxark 这个模型应该没有天气功能吧-。-
    jelinet
        20
    jelinet  
       96 天前
    我操作了一遍,/new 没反应。。。
    然后我在 CHAT_WHITE_LIST 里面把我自己删了,突然就收到了好多好多的『你没有权限操作。。。』
    是 sendMessageToTelegram 卡住了还是 sendMessageToChatGPT 卡住了?跟访问的 ip 有关系吗?我平常上飞机的 ip 并不能上 chatGPT 。
    Lentin
        21
    Lentin  
       96 天前 via iPhone
    jelinet
        22
    jelinet  
       96 天前
    连『新的对话开始』这句都没返回,感觉还没走到 sendMessageToChatGPT
    Lentin
        23
    Lentin  
       96 天前 via iPhone
    @jelinet 需要添加 KV 变量,并且 KV 的变量名需要为 DATABASE 并且绑定实例
    tbxark
        24
    tbxark  
    OP
       96 天前
    @jelinet 你试一下在 cloudflare 上面直接调试

    POST: https://your_workers_name.your_workers_subdomain.workers.dev/tetegram/:token/webhook

    Body
    ```js
    {
    "message": {
    "text": "/new",
    "chat": {
    "id": 你的 ID
    }
    }
    }
    ```
    jelinet
        25
    jelinet  
       96 天前
    搞定,是『 DATABASE 』名字设置错了。
    Lavria
        26
    Lavria  
       96 天前
    大佬我是第一次用 cloudflare ,照着流程走了一圈下来在第六步绑定阶段返回错误 {"ok":false,"error_code":400,"description":"Bad Request: invalid webhook URL specified"} 可能是哪里出了问题?
    tbxark
        27
    tbxark  
    OP
       96 天前 via iPhone
    @Lavria WORKERS_DOMAIN 设置了吗。不要加 https 。只写域名
    inertia
        28
    inertia  
       96 天前
    请问能动态地修改 “system" 项吗
    tbxark
        29
    tbxark  
    OP
       96 天前 via iPhone
    @inertia 可以,一会加一个这个指令
    inertia
        30
    inertia  
       96 天前
    @tbxark 赞!方便的话能不能把 temperature 这样的参数也做成可调节的呢?
    shuxiaokai
        31
    shuxiaokai  
       96 天前
    关注了! star+follow
    hanqian
        32
    hanqian  
       96 天前 via iPhone
    同求修改 system 项,这才是最好玩的
    tbxark
        33
    tbxark  
    OP
       96 天前 via iPhone
    @hanqian 假装有一个自定义的女朋友吗😂
    hanqian
        34
    hanqian  
       96 天前
    @tbxark 没错😂各种角色扮演都很方便
    GP1
        35
    GP1  
       96 天前   ❤️ 1
    有几个坑需要再仔细说明优化下:
    1. CHAT_WHITE_LIST 机器人 ID 怎么获取
    2. WORKERS_DOMAIN 去掉 https://
    3. KV Namespace Bindings
    Variable name=DATABASE

    你的这些设置哪几个需要在 js 文件里面设置,哪些需要在 Cloudflare Workers 里面设置再讲清楚点。
    Lavria
        36
    Lavria  
       96 天前
    @tbxark 谢谢,已经跑起来啦。😀
    Rrrrrr
        37
    Rrrrrr  
       96 天前
    跑起来了,那个 CHAT_WHITE_LIST 确实有点坑
    tbxark
        38
    tbxark  
    OP
       96 天前
    @GP1 @Rrrrrr id 可以随便聊一句,然后就会提示你你的 ID 是什么,确实没有其他渠道可以获取
    Hsinyao
        39
    Hsinyao  
       96 天前
    强,想学 js 了
    a881883
        40
    a881883  
       96 天前
    @tbxark 好像没有提示,是直接向机器人发消息吗?
    tbxark
        41
    tbxark  
    OP
       96 天前
    @a881883 对随便说一句话就会报错,让你把你的 ID 加到白名单
    pppxyz
        42
    pppxyz  
       96 天前
    请问如何清除对话重新开始?删除对话,关掉 bot,关掉 app 都不行。。。
    tbxark
        43
    tbxark  
    OP
       96 天前
    @pppxyz bug 已修复,请更新脚本,然后用 /new 就可以了
    tbxark
        44
    tbxark  
    OP
       96 天前   ❤️ 1
    @hanqian @inertia 自定义 system 和 temperature 都加上了,详情请看 readme
    whitegerry
        45
    whitegerry  
       96 天前
    CHAT_WHITE_LIST 只添加自己的 id 就可以聊天,之前 readme 说要机器人 id 找半天没找到
    a881883
        46
    a881883  
       96 天前
    @tbxark #41 原来是要 init 一次之后发才行,谢谢楼主,跑起来了
    tbxark
        47
    tbxark  
    OP
       96 天前
    @whitegerry 部署完随便和机器人说一句话,机器人就回吧 ID 告诉你
    pppxyz
        48
    pppxyz  
       96 天前
    我不知道该怎么回答
    >Cannot read properties of undefined (reading '0')} 请问这是什么原因?
    glamoroGG
        49
    glamoroGG  
       96 天前
    成功了
    zanxj
        50
    zanxj  
       96 天前
    @tbxark #41 第一次玩,怎么加入白名单啊?😂
    whitegerry
        51
    whitegerry  
       96 天前
    @tbxark 发一个 post 过去,返回结果里面有俩 id, 一个是自己的,另一个就是机器人的吧?那个 id 跟 token 里面那个数字串是一样的
    tbxark
        52
    tbxark  
    OP
       96 天前
    @zanxj 鉴于很多人不想设置白名单,或者不知道怎么获取 ID ,所以设置 I_AM_A_GENEROUS_PERSON 这个选项就能允许所有人访问, 值为 true 时生效, 或者你想设置的话,你就看看我新写的文档
    tbxark
        53
    tbxark  
    OP
       96 天前
    @whitegerry 不是那个, 不在白名单,你直接和机器人聊天,他就会回复“你没有权限使用这个命令, 请请联系管理员添加你的 ID(___你的 ID__)到白名单”, 或者你更新一下代码可以设置 I_AM_A_GENEROUS_PERSON 为 true 这个选项就能允许所有人访问。
    zanxj
        54
    zanxj  
       96 天前
    @tbxark #52 搞定了,谢谢!就是出现和 48 楼一样问题是怎么回事?你有在 TG ( t.me/ChatGPTChineseCommunity )群里吗
    whitegerry
        55
    whitegerry  
       96 天前 via iPhone
    @tbxark 之前只加了自己的 id 进去,发现可以聊就没多想了,机器人 id 加不加感觉无所谓吧?
    tbxark
        56
    tbxark  
    OP
       96 天前
    @zanxj @pppxyz 就是 chatapi 的返回值里的没有数据吧,具体错误我没有返回,我可以优化一下这个代码,把 openai 的错误信息返回。
    cicked
        57
    cicked  
       96 天前
    请问第三步的第 3 点 粘贴代码 这个代码在哪里呀
    cicked
        58
    cicked  
       96 天前
    请忽略上面的问题,我犯二了,已经成功部署, 谢谢 op
    acupnocup
        59
    acupnocup  
       96 天前
    哇 好厉害,fork+star
    xuesumin
        60
    xuesumin  
       96 天前
    谢谢,已 star
    stcheng
        61
    stcheng  
       96 天前
    感恩 deploy 很顺利丝滑
    yangfan1999
        62
    yangfan1999  
       96 天前
    部署文档很清晰,已配置上。
    ShirolinEX
        63
    ShirolinEX  
       96 天前
    关注了,等有空摸鱼搞一波
    SenLief
        64
    SenLief  
       96 天前
    为什么我初始化的时候是拒绝连接啊
    ysjiang4869
        65
    ysjiang4869  
       96 天前 via Android
    我也写了一个基于 tg bot 的,不过基于 aws lambda 做的
    FaiChou
        66
    FaiChou  
       96 天前
    我记得 telegram 机器人返回是有字符限制的? 4000 个字符来着? 如果有超过 4000 字符, 记得拆分返回.
    bjzhush
        67
    bjzhush  
       96 天前
    捣鼓了一个半小时才部署成功,没怎么搞过 cloudflare 的 worker ,感谢 OP 的分享!
    bjzhush
        68
    bjzhush  
       96 天前
    请问 OP ,如何查看已有的对话列表呢?我在 chatGPT 网页版没看到,问 GPT 回答说不知道
    tbxark
        69
    tbxark  
    OP
       96 天前
    @FaiChou 是有,还没研究怎么处理,所以现在聊太久了就得开启新会话
    tbxark
        70
    tbxark  
    OP
       96 天前
    @bjzhush 在 cloudflare 直接看 KV 数据库的内容?
    caqiko
        71
    caqiko  
       96 天前
    谢谢 OP 分享,整个部署过程非常流畅,20 分钟搞定一个 Bot
    👍👍👍
    bjzhush
        72
    bjzhush  
       96 天前
    @tbxark 去看了下 KV 数据库的内容,里面记录了我所有对话的内容,JSON 格式,可读性比较差,看不了对话列表,不过初级使用是够了
    tbxark
        73
    tbxark  
    OP
       96 天前 via iPhone
    @bjzhush 这么记录比较稳,能保证解析成功,还能一个 key 保存
    cdd2zju
        74
    cdd2zju  
       96 天前
    15 分钟部署成功,感谢 OP ,可以随时随地问 chatgpt 问题,太爽了。
    meishiwanwan520
        75
    meishiwanwan520  
       96 天前
    搭建完毕,感谢 OP 的奉献
    根据自己需求搭建了多个 bot 配合使用
    现在有个使用痛点,不知有没有解决思路
    1 、有没有办法可以针对某一个对话,快速全选复制或者转发、导出(目前是 bot 中多选,然后复制)
    2 、有没有可能知道建立了多少次的对话,或者编号。
    3 、在 2 的基础上,有没有可能像网页版,列出项目,对以前的对话进行追问
    纯属自己的需求,希望 OP 能给条思路,感谢
    JohnChang
        76
    JohnChang  
       96 天前   ❤️ 1
    谢谢,小白 step by step ,遇到的阻碍如下

    1.6 在 Telegram 中找到 BotFather, 发送 /setcommands, 找到自己的机器人, 发送 new - 开始新对话
    不论是「 new 」还是「 new - 」还是「/new 」,均没有反应
    实际知行:未执行

    3.2 Create a Worker
    没有「 Create a Worker 」,应该是「 Create a Service 」?
    实际执行:点「 Create a Service 」创建了服务

    3.3 将代码复制到编辑器中,保存
    此处没有编辑器
    实际执行:创建服务后,点击「 Quick edit 」用「 index.js 」的代码替换了原默认代码。

    4.5 CHAT_WHITE_LIST:设置成你想要使用机器人的聊天 ID ,例如 123456789,987654321 ,可以在 Telegram 中使用 /new 指令获取
    在哪里使用? Botfather 和自己的机器人均无反应
    实际执行:未执行

    5.5 在 KV Namespace Bindings 中点击 Edit variables
    没有「 Edit variables 」,有「 add Bindings 」
    实际执行:点「 add Bindings 」执行了 Bindings


    最终执行后,打开页面显示结果为
    [{"ok":true,"result":true,"description":"Webhook was set"}]
    xenme
        77
    xenme  
       96 天前 via iPhone
    啥时候出个 docker 版本的,只想开起来用,懒人党
    JohnChang
        78
    JohnChang  
       96 天前
    哦哦,发现实际已经成功运行了,还以为得点个按扭进行个绑定操作。。
    huaji
        79
    huaji  
       96 天前
    谢谢 O(∩_∩)O 谢谢,成功,已经在问她今晚吃什么了
    imldy
        80
    imldy  
       96 天前
    感谢楼主,但是 ChatGPT 完善了的使用说明有坑
    kld13
        81
    kld13  
       96 天前
    请问遇到这种情况该怎么办:

    OpenAI API 错误
    > This model's maximum context length is 4096 tokens. However, your messages resulted in 4262 tokens. Please reduce the length of the messages.}
    cicked
        82
    cicked  
       96 天前
    @kld13 /new 发起新对话就好了
    Yanel
        83
    Yanel  
       96 天前
    非常感谢,已经部署成功
    ovtfkw
        84
    ovtfkw  
       95 天前
    太麻烦了,有没有一键的
    arthury007
        85
    arthury007  
       95 天前 via iPhone
    每次 /new 之后之前对话错误的矫正都会被重置,这个有办法把纠正保持下去吗?
    ultra
        86
    ultra  
       95 天前
    OpenAI API 错误
    > This model's maximum context length is 4096 tokens. However, your messages resulted in 4440 tokens. Please reduce the length of the messages.}
    ultra
        87
    ultra  
       95 天前
    @cicked /new 也会出现同样的错误
    arthury007
        88
    arthury007  
       95 天前 via iPhone
    这个 ai 没有网页版的强,这是 api 的限制吗?还是说要交钱的才是正常版?比如我问苹果去年春季发布会是什么时候?它一下回答 2020 的时间,一下回答 2021 的时间,问他去年是什么时候,一下回答 2020 ,一下回答 2021 ,怎么矫正都回答不了 2022 苹果春季发布会的时间,网页版就没这个问题。
    SenLief
        89
    SenLief  
       95 天前
    @arthury007 是简化版的。
    tbxark
        90
    tbxark  
    OP
       95 天前
    @ultra 之前清除上下文有点问题,现在可以更新一下脚本
    tbxark
        91
    tbxark  
    OP
       95 天前
    @kld13 可能聊太长了,可以用 /new 清除一下上下文信息
    ansonsiva
        92
    ansonsiva  
       95 天前
    作为菜鸟,折腾了半天,可能 cloudflare 的界面跟 op 的并不相同,所以很多地方跟说明都对不上,暂时可以使用了已经,感谢 op
    tbxark
        93
    tbxark  
    OP
       95 天前
    @ansonsiva 😂 有些步骤是 Github Copilot 帮我补全的, 可能名字不太一样,我完善一下。
    yaoshi
        94
    yaoshi  
       95 天前
    "在 Cloudflare Workers 的设置里 将这个 Token 填入 TELEGRAM_TOKEN 变量中",小白不太了解,这个怎么搞啊
    Lentin
        96
    Lentin  
       94 天前
    toobad666
        97
    toobad666  
       93 天前 via Android
    我半夜搭建了一个 确实不错 但是在群聊里面没反应 不知道哪里出问题了
    CHAT_GROUP_WHITE_LIST 也设置了
    Mason2021
        98
    Mason2021  
       93 天前
    成功设置好了,速度很快。
    请问可否增加实时联网功能吗?
    modrich
        99
    modrich  
       93 天前
    感谢,已经成功搭建
    jwautumn
        100
    jwautumn  
       92 天前
    已搞定 感谢
    1  2  
    关于   ·   帮助文档   ·   博客   ·   nftychat   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4764 人在线   最高记录 5634   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 49ms · UTC 07:21 · PVG 15:21 · LAX 00:21 · JFK 03:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.