├── .github
└── workflows
│ └── ci.yml
├── LICENSE
├── Makefile
├── README.md
├── discord
├── demo.png
├── mod.ts
└── readme.md
├── fetch
├── README.md
├── get.js
└── post.js
├── issues
├── components
│ ├── card.jsx
│ ├── layout.jsx
│ └── search.jsx
├── data
│ └── repositories.js
├── mod.js
├── pages
│ ├── 404.jsx
│ ├── api
│ │ └── issues.js
│ └── home.jsx
└── readme.md
├── json_html
├── README.md
└── mod.js
├── post_request
├── mod.js
└── readme.md
├── slack
├── cities.ts
├── demo.png
├── install.png
├── mod.ts
└── readme.md
├── telegram
├── README.md
├── demo.png
└── mod.ts
└── yaus
├── mod.tsx
├── readme.md
└── schema.gql
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | push:
4 | branches: [main]
5 | pull_request:
6 | branches: [main]
7 |
8 | jobs:
9 | lint:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Clone repository
13 | uses: actions/checkout@v3
14 | with:
15 | submodules: false
16 | persist-credentials: false
17 |
18 | - name: Install Deno
19 | run: |-
20 | curl -fsSL https://deno.land/x/install/install.sh | sh
21 | echo "$HOME/.deno/bin" >> $GITHUB_PATH
22 |
23 | - name: Format
24 | run: deno fmt --check
25 |
26 | - name: Lint
27 | run: deno lint --unstable
28 |
29 | - name: Type Check
30 | run: make check
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Deno Land Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Check TypeScript types.
2 | check:
3 | deno cache fetch/get.js
4 | deno cache fetch/post.js
5 | deno cache post_request/mod.js
6 | deno cache json_html/mod.js
7 | deno cache issues/mod.js
8 | deno cache discord/mod.ts
9 | deno cache slack/mod.ts
10 | deno cache yaus/mod.tsx
11 | deno cache telegram/mod.ts
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deno Deploy Examples
2 |
3 | This repository contains a list of examples for Deno Deploy.
4 |
5 | - [fetch](fetch) - Make outbound requests using the `fetch()` API.
6 | - [json_html](json_html) - Respond to requests with JSON or HTML.
7 | - [post_request](post_request) - Handle POST requests.
8 | - [slack](slack) - A Slack Slash Command example.
9 | - [discord](discord) - A Discord Slash Command example.
10 | - [yaus](yaus) - A URL shortener built on top of Deno Deploy and FaunaDB.
11 | - [issues](issues) - A server rendered (using JSX) website that displays issues
12 | with most comments of a GitHub repository.
13 | - [telegram](telegram) - A Telegram Bot Command example.
14 |
--------------------------------------------------------------------------------
/discord/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/fd36d69fc020470c41cc095183c5c3f2e6a4c00f/discord/demo.png
--------------------------------------------------------------------------------
/discord/mod.ts:
--------------------------------------------------------------------------------
1 | // Sift is a small routing library that abstracts the details like registering
2 | // a fetch event listener and provides a simple function (serve) that has an
3 | // API to invoke a funciton for a specific path.
4 | import {
5 | json,
6 | serve,
7 | validateRequest,
8 | } from "https://deno.land/x/sift@0.4.2/mod.ts";
9 | // TweetNaCl is a cryptography library that we use to verify requests
10 | // from Discord.
11 | import nacl from "https://cdn.skypack.dev/tweetnacl@v1.0.3";
12 |
13 | // For all requests to "/" endpoint, we want to invoke home() handler.
14 | serve({
15 | "/": home,
16 | });
17 |
18 | // The main logic of the Discord Slash Command is defined in this function.
19 | async function home(request: Request) {
20 | // validateRequest() ensures that a request is of POST method and
21 | // has the following headers.
22 | const { error } = await validateRequest(request, {
23 | POST: {
24 | headers: ["X-Signature-Ed25519", "X-Signature-Timestamp"],
25 | },
26 | });
27 | if (error) {
28 | return json({ error: error.message }, { status: error.status });
29 | }
30 |
31 | // verifySignature() verifies if the request is coming from Discord.
32 | // When the request's signature is not valid, we return a 401 and this is
33 | // important as Discord sends invalid requests to test our verification.
34 | const { valid, body } = await verifySignature(request);
35 | if (!valid) {
36 | return json(
37 | { error: "Invalid request" },
38 | {
39 | status: 401,
40 | },
41 | );
42 | }
43 |
44 | const { type = 0, data = { options: [] } } = JSON.parse(body);
45 | // Discord performs Ping interactions to test our application.
46 | // Type 1 in a request implies a Ping interaction.
47 | if (type === 1) {
48 | return json({
49 | type: 1, // Type 1 in a response is a Pong interaction response type.
50 | });
51 | }
52 |
53 | // Type 2 in a request is an ApplicationCommand interaction.
54 | // It implies that a user has issued a command.
55 | if (type === 2) {
56 | const { value } = data.options.find(
57 | (option: { name: string }) => option.name === "name",
58 | );
59 | return json({
60 | // Type 4 reponds with the below message retaining the user's
61 | // input at the top.
62 | type: 4,
63 | data: {
64 | content: `Hello, ${value}!`,
65 | },
66 | });
67 | }
68 |
69 | // We will return a bad request error as a valid Discord request
70 | // shouldn't reach here.
71 | return json({ error: "bad request" }, { status: 400 });
72 | }
73 |
74 | /** Verify whether the request is coming from Discord. */
75 | async function verifySignature(
76 | request: Request,
77 | ): Promise<{ valid: boolean; body: string }> {
78 | const PUBLIC_KEY = Deno.env.get("DISCORD_PUBLIC_KEY")!;
79 | // Discord sends these headers with every request.
80 | const signature = request.headers.get("X-Signature-Ed25519")!;
81 | const timestamp = request.headers.get("X-Signature-Timestamp")!;
82 | const body = await request.text();
83 | const valid = nacl.sign.detached.verify(
84 | new TextEncoder().encode(timestamp + body),
85 | hexToUint8Array(signature),
86 | hexToUint8Array(PUBLIC_KEY),
87 | );
88 |
89 | return { valid, body };
90 | }
91 |
92 | /** Converts a hexadecimal string to Uint8Array. */
93 | function hexToUint8Array(hex: string) {
94 | return new Uint8Array(hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16)));
95 | }
96 |
--------------------------------------------------------------------------------
/discord/readme.md:
--------------------------------------------------------------------------------
1 | # Discord Slash Command
2 |
3 | A simple Discord Slash Command.
4 |
5 |
6 |
7 | Read [the tutorial](https://deno.com/deploy/docs/tutorial-discord-slash) to
8 | learn how to create and deploy a Discord Slash Command.
9 |
--------------------------------------------------------------------------------
/fetch/README.md:
--------------------------------------------------------------------------------
1 | # fetch() examples
2 |
3 | The examples here demonstrate how to make fetch() requests.
4 |
5 | - [`GET`](get.js) - makes a GET request to GitHub API endpoint.
6 | - [`POST`](post.js) - makes a POST request to https://post.deno.dev (echoes the
7 | request body back).
8 |
--------------------------------------------------------------------------------
/fetch/get.js:
--------------------------------------------------------------------------------
1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts";
2 |
3 | async function handleRequest(_request) {
4 | // We pass the url as the first argument to fetch and an object with
5 | // additional info like headers, method, and body for POST requests as
6 | // the second argument. By default fetch makes a GET request,
7 | // so we can skip specifying method for GET requests.
8 | const response = await fetch("https://api.github.com/users/denoland", {
9 | headers: {
10 | // Servers use this header to decide on response body format.
11 | // "application/json" implies that we accept the data in JSON format.
12 | accept: "application/json",
13 | },
14 | });
15 |
16 | // The .ok property of response indicates that the request is
17 | // successful (status is in range of 200-299).
18 | if (response.ok) {
19 | // response.json() method reads the body and parses it as JSON.
20 | // It then returns the data in JavaScript object.
21 | const { name, login, avatar_url: avatar } = await response.json();
22 | return new Response(
23 | JSON.stringify({ name, username: login, avatar }),
24 | {
25 | headers: {
26 | "content-type": "application/json; charset=UTF-8",
27 | },
28 | },
29 | );
30 | }
31 | // fetch() doesn't throw for bad status codes. You need to handle them
32 | // by checking if the response.ok is true or false.
33 | // In this example we're just returning a generic error for simplicity but
34 | // you might want to handle different cases based on response status code.
35 | return new Response(
36 | JSON.stringify({ message: "couldn't process your request" }),
37 | {
38 | status: 500,
39 | headers: {
40 | "content-type": "application/json; charset=UTF-8",
41 | },
42 | },
43 | );
44 | }
45 |
46 | console.log("Listening on http://localhost:8080");
47 | await listenAndServe(":8080", handleRequest);
48 |
--------------------------------------------------------------------------------
/fetch/post.js:
--------------------------------------------------------------------------------
1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts";
2 |
3 | async function handleRequest(_request) {
4 | // For making a POST request we need to specify the method property
5 | // as POST and provide data to the body property in the same object.
6 | // https://post.deno.dev echoes data we POST to it.
7 | const response = await fetch("https://post.deno.dev", {
8 | method: "POST",
9 | headers: {
10 | // This headers implies to the server that the content of
11 | // body is JSON and is encoded using UTF-8.
12 | "content-type": "application/json; charset=UTF-8",
13 | },
14 | body: JSON.stringify({
15 | message: "Hello from Deno Deploy.",
16 | }),
17 | });
18 |
19 | if (response.ok) {
20 | // The echo server returns the data back in
21 | const {
22 | json: { message },
23 | } = await response.json();
24 | return new Response(JSON.stringify({ message }), {
25 | headers: {
26 | "content-type": "application/json; charset=UTF-8",
27 | },
28 | });
29 | }
30 |
31 | return new Response(
32 | JSON.stringify({ message: "couldn't process your request" }),
33 | {
34 | status: 500,
35 | headers: {
36 | "content-type": "application/json; charset=UTF-8",
37 | },
38 | },
39 | );
40 | }
41 |
42 | console.log("Listening on http://localhost:8080");
43 | await listenAndServe(":8080", handleRequest);
44 |
--------------------------------------------------------------------------------
/issues/components/card.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import { formatDistanceToNow } from "https://cdn.skypack.dev/pin/date-fns@v2.16.1-IRhVs8UIgU3f9yS5Yt4w/date-fns.js";
3 |
4 | export default function Card({
5 | url,
6 | title,
7 | state,
8 | comments,
9 | createdAt,
10 | closedAt,
11 | }) {
12 | return (
13 |
14 |
15 | {title}
16 |
17 |
18 |
26 | {state === "closed"
27 | ? (
28 |
32 | )
33 | : (
34 |
38 | )}
39 |
40 |
41 |
46 |
50 |
51 |
{comments}
52 |
53 |
54 | {state === "closed"
55 | ? `closed ${formatDistanceToNow(new Date(closedAt))} ago`
56 | : `opened ${formatDistanceToNow(new Date(createdAt))} ago`}
57 |
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/issues/components/layout.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 |
3 | export default function Layout({ children }) {
4 | return (
5 |
6 |
7 |
8 |
12 |
16 |
17 |
18 | {children}
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/issues/components/search.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 |
3 | /** Validate if the form value follows the appropriate
4 | * structure (/). */
5 | function validateForm() {
6 | // deno-lint-ignore no-undef
7 | const repository = document.forms["search"]["repository"].value;
8 | if (repository.split("/").length !== 2) {
9 | alert(
10 | `Input should be in the form of 'owner/repository'. No forward slashes at the beginning or end.`,
11 | );
12 | return false;
13 | }
14 | return true;
15 | }
16 |
17 | export default function Search() {
18 | return (
19 |
20 |
21 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/issues/data/repositories.js:
--------------------------------------------------------------------------------
1 | // List (500) scrapped from gitstar-ranking.com/repositores.
2 | export default [
3 | "freeCodeCamp/freeCodeCamp",
4 | "996icu/996.ICU",
5 | "vuejs/vue",
6 | "EbookFoundation/free-programming-books",
7 | "facebook/react",
8 | "tensorflow/tensorflow",
9 | "twbs/bootstrap",
10 | "sindresorhus/awesome",
11 | "jwasham/coding-interview-university",
12 | "kamranahmedse/developer-roadmap",
13 | "getify/You-Dont-Know-JS",
14 | "ohmyzsh/ohmyzsh",
15 | "CyC2018/CS-Notes",
16 | "donnemartin/system-design-primer",
17 | "github/gitignore",
18 | "flutter/flutter",
19 | "Microsoft/vscode",
20 | "airbnb/javascript",
21 | "torvalds/linux",
22 | "d3/d3",
23 | "Snailclimb/JavaGuide",
24 | "facebook/react-native",
25 | "TheAlgorithms/Python",
26 | "vinta/awesome-python",
27 | "danistefanovic/build-your-own-x",
28 | "electron/electron",
29 | "trekhleb/javascript-algorithms",
30 | "facebook/create-react-app",
31 | "jlevy/the-art-of-command-line",
32 | "golang/go",
33 | "nodejs/node",
34 | "kubernetes/kubernetes",
35 | "justjavac/free-programming-books-zh_CN",
36 | "Microsoft/terminal",
37 | "ossu/computer-science",
38 | "angular/angular",
39 | "tensorflow/models",
40 | "Microsoft/TypeScript",
41 | "30-seconds/30-seconds-of-code",
42 | "mrdoob/three.js",
43 | "ant-design/ant-design",
44 | "FortAwesome/Font-Awesome",
45 | "laravel/laravel",
46 | "mui-org/material-ui",
47 | "iluwatar/java-design-patterns",
48 | "PanJiaChen/vue-element-admin",
49 | "MisterBooo/LeetCodeAnimation",
50 | "angular/angular.js",
51 | "avelino/awesome-go",
52 | "moby/moby",
53 | "vuejs/awesome-vue",
54 | "nvbn/thefuck",
55 | "zeit/next.js",
56 | "webpack/webpack",
57 | "storybooks/storybook",
58 | "goldbergyoni/nodebestpractices",
59 | "reduxjs/redux",
60 | "jquery/jquery",
61 | "apple/swift",
62 | "hakimel/reveal.js",
63 | "atom/atom",
64 | "django/django",
65 | "pallets/flask",
66 | "elastic/elasticsearch",
67 | "tonsky/FiraCode",
68 | "spring-projects/spring-boot",
69 | "socketio/socket.io",
70 | "chartjs/Chart.js",
71 | "shadowsocks/shadowsocks-windows",
72 | "expressjs/express",
73 | "typicode/json-server",
74 | "doocs/advanced-java",
75 | "chrislgarry/Apollo-11",
76 | "GoThinkster/realworld",
77 | "netdata/netdata",
78 | "rust-lang/rust",
79 | "adam-p/markdown-here",
80 | "kdn251/interviews",
81 | "httpie/httpie",
82 | "Semantic-Org/Semantic-UI",
83 | "gohugoio/hugo",
84 | "ElemeFE/element",
85 | "gatsbyjs/gatsby",
86 | "h5bp/html5-boilerplate",
87 | "josephmisiti/awesome-machine-learning",
88 | "lodash/lodash",
89 | "rails/rails",
90 | "resume/resume.github.com",
91 | "h5bp/Front-end-Developer-Interview-Questions",
92 | "redis/redis",
93 | "bitcoin/bitcoin",
94 | "ansible/ansible",
95 | "moment/moment",
96 | "kelseyhightower/nocode",
97 | "ReactiveX/RxJava",
98 | "pytorch/pytorch",
99 | "papers-we-love/papers-we-love",
100 | "apache/incubator-echarts",
101 | "gin-gonic/gin",
102 | "mtdvio/every-programmer-should-know",
103 | "scikit-learn/scikit-learn",
104 | "driftyco/ionic-framework",
105 | "meteor/meteor",
106 | "ReactTraining/react-router",
107 | "jgthms/bulma",
108 | "Microsoft/PowerToys",
109 | "jekyll/jekyll",
110 | "Hack-with-Github/Awesome-Hacking",
111 | "scutan90/DeepLearning-500-questions",
112 | "necolas/normalize.css",
113 | "google/material-design-icons",
114 | "NARKOZ/hacker-scripts",
115 | "enaqx/awesome-react",
116 | "ryanmcdermott/clean-code-javascript",
117 | "spring-projects/spring-framework",
118 | "jaywcjlove/awesome-mac",
119 | "neovim/neovim",
120 | "google/guava",
121 | "aymericdamien/TensorFlow-Examples",
122 | "yarnpkg/yarn",
123 | "wasabeef/awesome-android-ui",
124 | "ripienaar/free-for-dev",
125 | "scrapy/scrapy",
126 | "sindresorhus/awesome-nodejs",
127 | "square/okhttp",
128 | "Dogfalo/materialize",
129 | "prettier/prettier",
130 | "grafana/grafana",
131 | "minimaxir/big-list-of-naughty-strings",
132 | "serverless/serverless",
133 | "azl397985856/leetcode",
134 | "6to5/babel",
135 | "nwjs/nw.js",
136 | "juliangarnier/anime",
137 | "tesseract-ocr/tesseract",
138 | "home-assistant/core",
139 | "ageitgey/face_recognition",
140 | "square/retrofit",
141 | "MaximAbramchuck/awesome-interview-questions",
142 | "soimort/you-get",
143 | "FreeCodeCampChina/freecodecamp.cn",
144 | "ziishaned/learn-regex",
145 | "astaxie/build-web-application-with-golang",
146 | "vsouza/awesome-ios",
147 | "impress/impress.js",
148 | "gogits/gogs",
149 | "prakhar1989/awesome-courses",
150 | "TryGhost/Ghost",
151 | "nodejs/node-v0.x-archive",
152 | "git/git",
153 | "bailicangdu/vue2-elm",
154 | "godotengine/godot",
155 | "Alamofire/Alamofire",
156 | "zeit/hyper",
157 | "python/cpython",
158 | "trimstray/the-book-of-secret-knowledge",
159 | "sdmg15/Best-websites-a-programmer-should-visit",
160 | "k88hudson/git-flight-rules",
161 | "apache/dubbo",
162 | "JetBrains/kotlin",
163 | "prometheus/prometheus",
164 | "Unitech/pm2",
165 | "syncthing/syncthing",
166 | "justjavac/awesome-wechat-weapp",
167 | "facebook/jest",
168 | "AFNetworking/AFNetworking",
169 | "junegunn/fzf",
170 | "mozilla/pdf.js",
171 | "shadowsocks/shadowsocks",
172 | "adobe/brackets",
173 | "TheAlgorithms/Java",
174 | "iamkun/dayjs",
175 | "PhilJay/MPAndroidChart",
176 | "gulpjs/gulp",
177 | "karan/Projects",
178 | "discourse/discourse",
179 | "nestjs/nest",
180 | "google/material-design-lite",
181 | "hexojs/hexo",
182 | "styled-components/styled-components",
183 | "nuxt/nuxt.js",
184 | "apache/incubator-superset",
185 | "sahat/hackathon-starter",
186 | "pixijs/pixi.js",
187 | "alvarotrigo/fullPage.js",
188 | "BVLC/caffe",
189 | "tiimgreen/github-cheat-sheet",
190 | "blueimp/jQuery-File-Upload",
191 | "DefinitelyTyped/DefinitelyTyped",
192 | "JuliaLang/julia",
193 | "shadowsocks/shadowsocks-android",
194 | "XX-net/XX-Net",
195 | "Trinea/android-open-project",
196 | "koajs/koa",
197 | "bumptech/glide",
198 | "fastlane/fastlane",
199 | "huginn/huginn",
200 | "airbnb/lottie-android",
201 | "exacity/deeplearningbook-chinese",
202 | "videojs/video.js",
203 | "kamranahmedse/design-patterns-for-humans",
204 | "zenorocha/clipboard.js",
205 | "tldr-pages/tldr",
206 | "Leaflet/Leaflet",
207 | "isocpp/CppCoreGuidelines",
208 | "floodsung/Deep-Learning-Papers-Reading-Roadmap",
209 | "php/php-src",
210 | "RocketChat/Rocket.Chat",
211 | "shadowsocks/ShadowsocksX-NG",
212 | "photonstorm/phaser",
213 | "jondot/awesome-react-native",
214 | "fffaraz/awesome-cpp",
215 | "Blankj/AndroidUtilCode",
216 | "quilljs/quill",
217 | "apache/spark",
218 | "ariya/phantomjs",
219 | "lukehoban/es6features",
220 | "danielmiessler/SecLists",
221 | "dypsilon/frontend-dev-bookmarks",
222 | "alex/what-happens-when",
223 | "apachecn/AiLearning",
224 | "jashkenas/backbone",
225 | "Homebrew/legacy-homebrew",
226 | "Bilibili/ijkplayer",
227 | "xitu/gold-miner",
228 | "open-guides/og-aws",
229 | "joshbuchea/HEAD",
230 | "Marak/faker.js",
231 | "Mashape/kong",
232 | "certbot/certbot",
233 | "pandas-dev/pandas",
234 | "localstack/localstack",
235 | "codepath/android_guides",
236 | "ethereum/go-ethereum",
237 | "tastejs/todomvc",
238 | "ant-design/ant-design-pro",
239 | "dcloudio/uni-app",
240 | "bayandin/awesome-awesomeness",
241 | "caolan/async",
242 | "jakevdp/PythonDataScienceHandbook",
243 | "wg/wrk",
244 | "vuejs/vue-cli",
245 | "akullpp/awesome-java",
246 | "react-boilerplate/react-boilerplate",
247 | "zxing/zxing",
248 | "getsentry/sentry",
249 | "faif/python-patterns",
250 | "kenwheeler/slick",
251 | "aosabook/500lines",
252 | "shengxinjing/programmer-job-blacklist",
253 | "google/styleguide",
254 | "pingcap/tidb",
255 | "jashkenas/underscore",
256 | "JakeWharton/butterknife",
257 | "crossoverJie/JCSprout",
258 | "square/leakcanary",
259 | "dkhamsing/open-source-ios-apps",
260 | "nolimits4web/swiper",
261 | "postwoman-io/hoppscotch",
262 | "netty/netty",
263 | "astaxie/beego",
264 | "fzaninotto/Faker",
265 | "jiahaog/nativefier",
266 | "Tencent/weui",
267 | "vuejs/vuex",
268 | "request/request",
269 | "ocornut/imgui",
270 | "select2/select2",
271 | "fxsjy/jieba",
272 | "standard/standard",
273 | "Modernizr/Modernizr",
274 | "ryanoasis/nerd-fonts",
275 | "inboxapp/nylas-mail",
276 | "nvie/gitflow",
277 | "hashicorp/terraform",
278 | "ziadoz/awesome-php",
279 | "ZuzooVn/machine-learning-for-software-engineers",
280 | "brillout/awesome-react-components",
281 | "composer/composer",
282 | "mathiasbynens/dotfiles",
283 | "rethinkdb/rethinkdb",
284 | "johnpapa/angular-styleguide",
285 | "github/fetch",
286 | "symfony/symfony",
287 | "freeCodeCamp/devdocs",
288 | "herrbischoff/awesome-macos-command-line",
289 | "raywenderlich/swift-algorithm-club",
290 | "angular/angular-cli",
291 | "skylot/jadx",
292 | "facebookresearch/Detectron",
293 | "alibaba/p3c",
294 | "sharkdp/bat",
295 | "kelseyhightower/kubernetes-the-hard-way",
296 | "alibaba/arthas",
297 | "iview/iview",
298 | "Homebrew/brew",
299 | "danielgindi/Charts",
300 | "unknwon/the-way-to-go_ZH_CN",
301 | "IanLunn/Hover",
302 | "postcss/postcss",
303 | "SheetJS/sheetjs",
304 | "wearehive/project-guidelines",
305 | "ReactiveX/rxjs",
306 | "sequelize/sequelize",
307 | "GitSquared/edex-ui",
308 | "amix/vimrc",
309 | "tiangolo/fastapi",
310 | "GitbookIO/gitbook",
311 | "CodeHubApp/CodeHub",
312 | "PowerShell/PowerShell",
313 | "greenrobot/EventBus",
314 | "VincentGarreau/particles.js",
315 | "webtorrent/webtorrent",
316 | "cheeriojs/cheerio",
317 | "geekcompany/ResumeSample",
318 | "formulahendry/955.WLB",
319 | "mobxjs/mobx",
320 | "sentsin/layui",
321 | "gorhill/uBlock",
322 | "alibaba/fastjson",
323 | "alibaba/druid",
324 | "openai/gym",
325 | "naptha/tesseract.js",
326 | "laravel/framework",
327 | "airbnb/lottie-web",
328 | "gitlabhq/gitlabhq",
329 | "BurntSushi/ripgrep",
330 | "ajaxorg/ace",
331 | "tootsuite/mastodon",
332 | "terryum/awesome-deep-learning-papers",
333 | "alibaba/flutter-go",
334 | "harvesthq/chosen",
335 | "Microsoft/monaco-editor",
336 | "google/leveldb",
337 | "CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers",
338 | "rapid7/metasploit-framework",
339 | "drone/drone",
340 | "koalaman/shellcheck",
341 | "niklasvh/html2canvas",
342 | "FFmpeg/FFmpeg",
343 | "0xAX/linux-insides",
344 | "realpython/python-guide",
345 | "sorrycc/awesome-javascript",
346 | "vim/vim",
347 | "rstacruz/nprogress",
348 | "facebookresearch/fastText",
349 | "VundleVim/Vundle.vim",
350 | "hammerjs/hammer.js",
351 | "chubin/cheat.sh",
352 | "cmderdev/cmder",
353 | "Automattic/mongoose",
354 | "balderdashy/sails",
355 | "emberjs/ember.js",
356 | "akveo/ngx-admin",
357 | "ageron/handson-ml",
358 | "remy/nodemon",
359 | "codemirror/CodeMirror",
360 | "Polymer/polymer",
361 | "pyenv/pyenv",
362 | "JedWatson/react-select",
363 | "t4t5/sweetalert",
364 | "nlohmann/json",
365 | "pypa/pipenv",
366 | "fouber/blog",
367 | "donnemartin/interactive-coding-challenges",
368 | "junegunn/vim-plug",
369 | "ggreer/the_silver_searcher",
370 | "GoogleChrome/lighthouse",
371 | "docker/compose",
372 | "CymChad/BaseRecyclerViewAdapterHelper",
373 | "redux-saga/redux-saga",
374 | "facebook/flow",
375 | "hashicorp/vagrant",
376 | "jgm/pandoc",
377 | "kahun/awesome-sysadmin",
378 | "typicode/husky",
379 | "syl20bnr/spacemacs",
380 | "Tencent/wepy",
381 | "airbnb/lottie-ios",
382 | "mitmproxy/mitmproxy",
383 | "Netflix/Hystrix",
384 | "sindresorhus/awesome-electron",
385 | "hashicorp/consul",
386 | "powerline/fonts",
387 | "mqyqingfeng/Blog",
388 | "angular/components",
389 | "dimsemenov/PhotoSwipe",
390 | "Microsoft/calculator",
391 | "github/hub",
392 | "kriasoft/react-starter-kit",
393 | "MrRio/jsPDF",
394 | "jgraph/drawio",
395 | "bvaughn/react-virtualized",
396 | "alebcay/awesome-shell",
397 | "reduxjs/react-redux",
398 | "google/iosched",
399 | "bevacqua/dragula",
400 | "guzzle/guzzle",
401 | "dmlc/xgboost",
402 | "SwiftyJSON/SwiftyJSON",
403 | "Meituan-Dianping/mpvue",
404 | "dokku/dokku",
405 | "mochajs/mocha",
406 | "matteocrippa/awesome-swift",
407 | "influxdb/influxdb",
408 | "ReactiveCocoa/ReactiveCocoa",
409 | "markerikson/react-redux-links",
410 | "facebook/docusaurus",
411 | "fastai/fastai",
412 | "dotnet/aspnetcore",
413 | "facebookarchive/pop",
414 | "nagadomi/waifu2x",
415 | "donnemartin/data-science-ipython-notebooks",
416 | "ramda/ramda",
417 | "Reactive-Extensions/RxJS",
418 | "pcottle/learnGitBranching",
419 | "jadejs/pug",
420 | "usablica/intro.js",
421 | "vapor/vapor",
422 | "denysdovhan/wtfjs",
423 | "paularmstrong/normalizr",
424 | "trailofbits/algo",
425 | "tornadoweb/tornado",
426 | "railsware/upterm",
427 | "BradLarson/GPUImage",
428 | "julycoding/The-Art-Of-Programming-By-July",
429 | "kubernetes/minikube",
430 | "lukasz-madon/awesome-remote-job",
431 | "fengdu78/Coursera-ML-AndrewNg-Notes",
432 | "inconshreveable/ngrok",
433 | "kataras/iris",
434 | "tmux/tmux",
435 | "petkaantonov/bluebird",
436 | "odoo/odoo",
437 | "cockroachdb/cockroach",
438 | "FallibleInc/security-guide-for-developers",
439 | "OAI/OpenAPI-Specification",
440 | "wsargent/docker-cheat-sheet",
441 | "futurice/android-best-practices",
442 | "ReactiveX/RxSwift",
443 | "encode/django-rest-framework",
444 | "rollup/rollup",
445 | "NativeScript/NativeScript",
446 | "spf13/cobra",
447 | "jlmakes/scrollreveal",
448 | "ReactiveX/RxAndroid",
449 | "facebook/draft-js",
450 | "domnikl/DesignPatternsPHP",
451 | "SamyPesse/How-to-Make-a-Computer-Operating-System",
452 | "tj/commander.js",
453 | "apache/incubator-mxnet",
454 | "xkcoding/spring-boot-demo",
455 | "resin-io/etcher",
456 | "SeleniumHQ/selenium",
457 | "chenglou/react-motion",
458 | "postcss/autoprefixer",
459 | "ianstormtaylor/slate",
460 | "MostlyAdequate/mostly-adequate-guide",
461 | "apache/airflow",
462 | "swagger-api/swagger-ui",
463 | "mongodb/mongo",
464 | "veggiemonk/awesome-docker",
465 | "JohnCoates/Aerial",
466 | "vuejs/vue-next",
467 | "poteto/hiring-without-whiteboards",
468 | "google/gson",
469 | "ruanyf/es6tutorial",
470 | "Bilibili/flv.js",
471 | "segmentio/nightmare",
472 | "vuejs/vue-devtools",
473 | "sqlmapproject/sqlmap",
474 | "react-bootstrap/react-bootstrap",
475 | "google/googletest",
476 | "go-kit/kit",
477 | "hashicorp/vault",
478 | "google/web-starter-kit",
479 | "sampotts/plyr",
480 | "Prinzhorn/skrollr",
481 | "labstack/echo",
482 | "verekia/js-stack-from-scratch",
483 | "avajs/ava",
484 | "jamiebuilds/the-super-tiny-compiler",
485 | "komeiji-satori/Dress",
486 | "mbadolato/iTerm2-Color-Schemes",
487 | "ftlabs/fastclick",
488 | "ipfs/ipfs",
489 | "afollestad/material-dialogs",
490 | "keon/algorithms",
491 | "jorgebucaran/hyperapp",
492 | "lovell/sharp",
493 | "jaredhanson/passport",
494 | "stedolan/jq",
495 | "Seldaek/monolog",
496 | "bcit-ci/CodeIgniter",
497 | "localForage/localForage",
498 | "facebook/rocksdb",
499 | "felixrieseberg/windows95",
500 | "parse-community/parse-server",
501 | "google/python-fire",
502 | "dhg/Skeleton",
503 | ];
504 |
--------------------------------------------------------------------------------
/issues/mod.js:
--------------------------------------------------------------------------------
1 | import { serve } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import homePage from "./pages/home.jsx";
3 | import notFoundPage from "./pages/404.jsx";
4 | import issuesEndpoint from "./pages/api/issues.js";
5 |
6 | serve({
7 | "/": homePage,
8 | "/api/issues": issuesEndpoint,
9 | 404: notFoundPage,
10 | });
11 |
--------------------------------------------------------------------------------
/issues/pages/404.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import Layout from "../components/layout.jsx";
3 |
4 | export default function notFoundPage(request) {
5 | return (
6 |
7 |
8 |
Page not found
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/issues/pages/api/issues.js:
--------------------------------------------------------------------------------
1 | import { json, validateRequest } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import repositories from "../../data/repositories.js";
3 |
4 | export default async function issuesEndpoint(request) {
5 | // We will only allow GET requests to this endpoint.
6 | const { error } = await validateRequest(request, {
7 | GET: {},
8 | });
9 | if (error) {
10 | return json({ error: error.message }, { status: error.status });
11 | }
12 |
13 | // The user can provide a param named 'repository'
14 | // to fetch issues for that repository.
15 | const { searchParams } = new URL(request.url);
16 | const repository = searchParams.get("repository");
17 |
18 | // Get the issues from GitHub.
19 | const { issues, code, message } = await getIssues(repository);
20 | if (code === "repoNotFound") {
21 | return json(
22 | {
23 | message,
24 | },
25 | { status: 404 },
26 | );
27 | }
28 |
29 | // Return the message with 500 status code if GitHub token is not set.
30 | if (code === "tokenNotAvailable") {
31 | return json(
32 | {
33 | message,
34 | },
35 | { status: 500 },
36 | );
37 | }
38 |
39 | return json({ issues }, { status: 200 });
40 | }
41 |
42 | /** Get issues with most comments for the provided repository. Default
43 | * to a random one from the top (most stars) 500 repositories. */
44 | export async function getIssues(repository) {
45 | if (!repository) {
46 | repository = repositories[Math.floor(Math.random() * 500)];
47 | }
48 |
49 | // Retrieve GitHub API token from env. Error if not set.
50 | const token = Deno.env.get("GITHUB_TOKEN");
51 | if (!token) {
52 | return {
53 | code: "tokenNotAvailable",
54 | message: "Environment variable GITHUB_TOKEN not set.",
55 | };
56 | }
57 |
58 | // Fetch issues for the provided repository.
59 | const response = await fetch(
60 | `https://api.github.com/repos/${repository}/issues?sort=comments&per_page=10&state=all`,
61 | {
62 | method: "GET",
63 | headers: {
64 | "User-Agent": "Deno Deploy",
65 | Accept: "application/vnd.github.v3+json",
66 | Authorization: `token ${token}`,
67 | },
68 | },
69 | );
70 |
71 | // Handle if the response isn't successful.
72 | if (!response.ok) {
73 | // If the repository is not found, reflect that with a message.
74 | if (response.status === 404) {
75 | return {
76 | code: "repoNotFound",
77 | message: `Repository '${repository}' not found`,
78 | };
79 | } else {
80 | return {
81 | code: "serverError",
82 | message: `Failed to retrieve issues from GitHub. Try again.`,
83 | };
84 | }
85 | }
86 |
87 | // Return the issues.
88 | const issues = await response.json();
89 | return {
90 | repository,
91 | issues: issues.map((issue) => ({
92 | url: issue.html_url,
93 | title: issue.title,
94 | state: issue.state,
95 | comments: issue.comments,
96 | createdAt: issue.created_at,
97 | closedAt: issue.closed_at,
98 | })),
99 | };
100 | }
101 |
--------------------------------------------------------------------------------
/issues/pages/home.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import Card from "../components/card.jsx";
3 | import Layout from "../components/layout.jsx";
4 | import Search from "../components/search.jsx";
5 | import { getIssues } from "./api/issues.js";
6 |
7 | export default async function homePage(request) {
8 | const { searchParams } = new URL(request.url);
9 | const repo = searchParams.get("repository");
10 | const { repository, issues, code, message } = await getIssues(repo);
11 |
12 | return (
13 |
14 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/issues/readme.md:
--------------------------------------------------------------------------------
1 | # Most Discussed Issues
2 |
3 | A small server rendered website that displays the most discussed issues of a
4 | repository.
5 |
6 | Visit [`https://issues.deno.dev`](https://issues.deno.dev) for a live version.
7 |
8 | - [Deploy](#deploy)
9 | - [Run Offline](#run-offline)
10 |
11 | ## Deploy
12 |
13 | Follow the steps under [`GitHub`](#github) section to obtain a GitHub token and
14 | click on the button below to deploy the application.
15 |
16 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/issues/mod.js&env=GITHUB_TOKEN)
17 |
18 | ### GitHub
19 |
20 | The application uses GitHub API to fetch issues sorted by most number of
21 | comments. And we need the GitHub PAT (Personal Access Token) to communicate with
22 | the API.
23 |
24 | Here are the steps to obtain one:
25 |
26 | 1. Go to https://github.com/settings/tokens
27 | 2. Click on **Generate new token**
28 | 3. Fill the **Note** field for your own reference
29 | 4. Scroll down (don't select any scopes) and click on **Generate token**
30 |
31 | That's it. You now have a token that you can use with the application.
32 |
33 | ## Run Offline
34 |
35 | You can run the application on your local machine using
36 | [`deno`](https://github.com/denoland/deno).
37 |
38 | ```
39 | GITHUB_TOKEN= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/issues/mod.js
40 | ```
41 |
42 | Replace `` with you GitHub token.
43 |
--------------------------------------------------------------------------------
/json_html/README.md:
--------------------------------------------------------------------------------
1 | # Respond with JSON and/or HTML
2 |
3 | This example demonstrates how to respond to requests with JSON and/or HTML in
4 | Deno Deploy.
5 |
6 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/json_html/mod.js)
7 |
8 | - [Try Live Version](#try-live-version)
9 | - [Run offline](#run-offline)
10 |
11 | ## Try Live Version
12 |
13 | The example is deployed at https://json-html.deno.dev for demo.
14 |
15 | Visit or curl `https://json-html.deno.dev/json` endpoint to get response in
16 | JSON.
17 |
18 | ```sh
19 | curl --dump-header - https://json-html.deno.dev/json
20 | # Response:
21 |
22 | # HTTP/2 200
23 | # content-type: application/json; charset=UTF-8
24 | # content-length: 36
25 | # date: Tue, 09 Mar 2021 15:11:57 GMT
26 | # server: denosr
27 | # x-dsr-id: asia-southeast1-a::runner-l4hc
28 | # {"message":"Hello from Deno Deploy"}
29 | ```
30 |
31 | Visit or curl `https://json-html.deno.dev/html` endpoint to get response in
32 | HTML.
33 |
34 | ```sh
35 | curl --dump-header - https://json-html.deno.dev/html
36 | # Response:
37 |
38 | # HTTP/2 200
39 | # content-type: text/html; charset=UTF-8
40 | # content-length: 73
41 | # date: Tue, 09 Mar 2021 15:15:56 GMT
42 | # server: denosr
43 | # x-dsr-id: asia-southeast1-a::runner-l4hc
44 | #
45 | # Message: Hello from Deno Deploy.
46 | #
47 | ```
48 |
49 | ## Run Offline
50 |
51 | You can run the example program on your machine using
52 | [`deno`](https://github.com/denoland/deno):
53 |
54 | ```sh
55 | deno run https://raw.githubusercontent.com/denoland/deploy_examples/main/json_html/mod.js
56 | # Listening at http://localhost:8080
57 | ```
58 |
--------------------------------------------------------------------------------
/json_html/mod.js:
--------------------------------------------------------------------------------
1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts";
2 |
3 | function handleRequest(request) {
4 | const { pathname } = new URL(request.url);
5 |
6 | // Respond with HTML
7 | if (pathname.startsWith("/html")) {
8 | const html = `
9 | Message: Hello from Deno Deploy.
10 | `;
11 |
12 | return new Response(html, {
13 | headers: {
14 | // The interpretation of the body of the response by the client depends
15 | // on the 'content-type' header.
16 | // The "text/html" part implies to the client that the content is HTML
17 | // and the "charset=UTF-8" part implies to the client that the content
18 | // is encoded using UTF-8.
19 | "content-type": "text/html; charset=UTF-8",
20 | },
21 | });
22 | }
23 |
24 | // Respond with JSON
25 | if (pathname.startsWith("/json")) {
26 | // Use stringify function to convert javascript object to JSON string.
27 | const json = JSON.stringify({
28 | message: "Hello from Deno Deploy",
29 | });
30 |
31 | return new Response(json, {
32 | headers: {
33 | "content-type": "application/json; charset=UTF-8",
34 | },
35 | });
36 | }
37 |
38 | return new Response(
39 | `
43 | Return JSON and/or HTML Example
44 |
45 | /html - responds with HTML to the request.
46 |
47 |
48 | /json - responds with JSON to the request.
49 |
50 | `,
51 | {
52 | headers: {
53 | "content-type": "text/html; charset=UTF-8",
54 | },
55 | },
56 | );
57 | }
58 |
59 | console.log("Listening on http://localhost:8080");
60 | await listenAndServe(":8080", handleRequest);
61 |
--------------------------------------------------------------------------------
/post_request/mod.js:
--------------------------------------------------------------------------------
1 | // Every request to a Deno Deploy program is considered as a fetch event.
2 | // So let's register our listener that will respond with the result of
3 | // our request handler on "fetch" events.
4 | addEventListener("fetch", (event) => {
5 | event.respondWith(handleRequest(event.request));
6 | });
7 |
8 | async function handleRequest(request) {
9 | if (request.method !== "POST") {
10 | return new Response(null, {
11 | status: 405,
12 | statusText: "Method Not Allowed",
13 | });
14 | }
15 |
16 | // We want the 'content-type' header to be present to be able to determine
17 | // the type of data sent by the client. So we respond to the client with
18 | // "Bad Request" status if the header is not available on the request.
19 | if (!request.headers.has("content-type")) {
20 | return new Response(
21 | JSON.stringify({ error: "please provide 'content-type' header" }),
22 | {
23 | status: 400,
24 | statusText: "Bad Request",
25 | headers: {
26 | "Content-Type": "application/json; charset=utf-8",
27 | },
28 | },
29 | );
30 | }
31 |
32 | const contentType = request.headers.get("content-type");
33 | const responseInit = {
34 | headers: {
35 | "Content-Type": "application/json; charset=utf-8",
36 | },
37 | };
38 |
39 | // Handle JSON data.
40 | if (contentType.includes("application/json")) {
41 | const json = await request.json();
42 | return new Response(JSON.stringify({ json }, null, 2), responseInit);
43 | }
44 |
45 | // Handle form data.
46 | if (
47 | contentType.includes("application/x-www-form-urlencoded") ||
48 | contentType.includes("multipart/form-data")
49 | ) {
50 | const formData = await request.formData();
51 | const formDataJSON = {};
52 | for (const [key, value] of formData.entries()) {
53 | formDataJSON[key] = value;
54 | }
55 | return new Response(
56 | JSON.stringify({ form: formDataJSON }, null, 2),
57 | responseInit,
58 | );
59 | }
60 |
61 | // Handle plain text.
62 | if (contentType.includes("text/plain")) {
63 | const text = await request.text();
64 | return new Response(JSON.stringify({ text }, null, 2), responseInit);
65 | }
66 |
67 | // Reaching here implies that we don't support the provided content-type
68 | // of the request so we reflect that back to the client.
69 | return new Response(null, {
70 | status: 415,
71 | statusText: "Unsupported Media Type",
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/post_request/readme.md:
--------------------------------------------------------------------------------
1 | # Handle a POST Request
2 |
3 | This example demonstrates how to handle POST requests in Deno Deploy.
4 |
5 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/post_request/mod.js)
6 |
7 | - [Try Live Version](#try-live-version)
8 | - [Run offline](#run-offline)
9 |
10 | ## Try Live Version
11 |
12 | The example is deployed at https://post.deno.dev for demo.
13 |
14 | A POST request with JSON body:
15 |
16 | ```sh
17 | curl -X POST \
18 | -H 'content-type: application/json' \
19 | -d '{ "name": "Deno" }' https://post.deno.dev
20 | ```
21 |
22 | Response:
23 |
24 | ```json
25 | {
26 | "json": {
27 | "name": "Deno"
28 | }
29 | }
30 | ```
31 |
32 | A POST request with form data:
33 |
34 | ```sh
35 | curl -X POST -F 'name=Deno' https://post.deno.dev
36 | ```
37 |
38 | Response:
39 |
40 | ```json
41 | {
42 | "form": {
43 | "name": "Deno"
44 | }
45 | }
46 | ```
47 |
48 | ## Run Offline
49 |
50 | You can run the example program on your machine using
51 | [`deno`](https://github.com/denoland/deno):
52 |
53 | ```sh
54 | deno run https://raw.githubusercontent.com/denoland/deploy_examples/main/post_request/mod.js
55 | ```
56 |
--------------------------------------------------------------------------------
/slack/cities.ts:
--------------------------------------------------------------------------------
1 | // Most populated cities and corresponding openweathermap.org city ID.
2 | export const cities = [
3 | { id: 98182, name: "Baghdad" },
4 | { id: 108410, name: "Riyadh" },
5 | { id: 112931, name: "Tehran" },
6 | { id: 160263, name: "Dar es Salaam" },
7 | { id: 188714, name: "Luanda" },
8 | { id: 360630, name: "Cairo" },
9 | { id: 361058, name: "Alexandria" },
10 | { id: 379252, name: "Khartoum" },
11 | { id: 379253, name: "Khartoum" },
12 | { id: 498817, name: "Saint Petersburg" },
13 | { id: 524894, name: "Moscow" },
14 | { id: 524901, name: "Moscow" },
15 | { id: 536203, name: "Saint Petersburg" },
16 | { id: 686502, name: "Alexandria" },
17 | { id: 745044, name: "Istanbul" },
18 | { id: 993800, name: "Johannesburg" },
19 | { id: 1023365, name: "Alexandria" },
20 | { id: 1172451, name: "Lahore" },
21 | { id: 1174872, name: "Karachi" },
22 | { id: 1176734, name: "Hyderabad" },
23 | { id: 1185241, name: "Dhaka" },
24 | { id: 1259229, name: "Pune" },
25 | { id: 1264527, name: "Chennai" },
26 | { id: 1269843, name: "Hyderabad" },
27 | { id: 1273294, name: "Delhi" },
28 | { id: 1275004, name: "Kolkata" },
29 | { id: 1275339, name: "Mumbai" },
30 | { id: 1279233, name: "Ahmedabad" },
31 | { id: 1298824, name: "Yangon" },
32 | { id: 1337178, name: "Dhaka" },
33 | { id: 1609348, name: "Bangkok" },
34 | { id: 1609350, name: "Bangkok" },
35 | { id: 1642911, name: "Jakarta" },
36 | { id: 1687800, name: "Santiago" },
37 | { id: 1687801, name: "Santiago" },
38 | { id: 1687812, name: "Santiago" },
39 | { id: 1687818, name: "Santiago" },
40 | { id: 1687825, name: "Santiago" },
41 | { id: 1687827, name: "Santiago" },
42 | { id: 1687834, name: "Santiago" },
43 | { id: 1701668, name: "Manila" },
44 | { id: 1704129, name: "Madrid" },
45 | { id: 1705545, name: "Los Angeles" },
46 | { id: 1726701, name: "Barcelona" },
47 | { id: 1726705, name: "Barcelona" },
48 | { id: 1726707, name: "Barcelona" },
49 | { id: 1726708, name: "Barcelona" },
50 | { id: 1733046, name: "Kuala Lumpur" },
51 | { id: 1735161, name: "Kuala Lumpur" },
52 | { id: 1791247, name: "Wuhan" },
53 | { id: 1792947, name: "Tianjin" },
54 | { id: 1793743, name: "Suzhou" },
55 | { id: 1795563, name: "Shenzhen" },
56 | { id: 1795564, name: "Shenzhen" },
57 | { id: 1795565, name: "Shenzhen" },
58 | { id: 1796236, name: "Shanghai" },
59 | { id: 1797929, name: "Qingdao" },
60 | { id: 1799962, name: "Nanjing" },
61 | { id: 1799963, name: "Nanjing" },
62 | { id: 1805753, name: "Jinan" },
63 | { id: 1808926, name: "Hangzhou" },
64 | { id: 1809858, name: "Guangzhou" },
65 | { id: 1811103, name: "Foshan" },
66 | { id: 1812531, name: "Dongguan" },
67 | { id: 1812537, name: "Dongguan" },
68 | { id: 1812543, name: "Dongguan" },
69 | { id: 1812544, name: "Dongguan" },
70 | { id: 1812545, name: "Dongguan" },
71 | { id: 1814087, name: "Dalian" },
72 | { id: 1814906, name: "Chongqing" },
73 | { id: 1815286, name: "Chengdu" },
74 | { id: 1816670, name: "Beijing" },
75 | { id: 1819729, name: "Hong Kong" },
76 | { id: 1835847, name: "Seoul" },
77 | { id: 1835848, name: "Seoul" },
78 | { id: 1850147, name: "Tokyo" },
79 | { id: 1853908, name: "Osaka" },
80 | { id: 1853909, name: "Osaka" },
81 | { id: 1856057, name: "Nagoya" },
82 | { id: 1863967, name: "Fukuoka" },
83 | { id: 1880252, name: "Singapore" },
84 | { id: 1886760, name: "Suzhou" },
85 | { id: 1919759, name: "Nanjing" },
86 | { id: 2034161, name: "Dalian" },
87 | { id: 2034937, name: "Shenyang" },
88 | { id: 2037013, name: "Harbin" },
89 | { id: 2147854, name: "Surat" },
90 | { id: 2240449, name: "Luanda" },
91 | { id: 2267226, name: "Lagos" },
92 | { id: 2270968, name: "Belo Horizonte" },
93 | { id: 2314302, name: "Kinshasa" },
94 | { id: 2332459, name: "Lagos" },
95 | { id: 2643743, name: "London" },
96 | { id: 2646507, name: "Houston" },
97 | { id: 2657513, name: "Alexandria" },
98 | { id: 2734637, name: "Santiago" },
99 | { id: 2968815, name: "Paris" },
100 | { id: 2988506, name: "Paris" },
101 | { id: 2988507, name: "Paris" },
102 | { id: 3117735, name: "Madrid" },
103 | { id: 3121070, name: "Guadalajara" },
104 | { id: 3128760, name: "Barcelona" },
105 | { id: 3405814, name: "Belo Horizonte" },
106 | { id: 3405825, name: "Belo Horizonte" },
107 | { id: 3407977, name: "Alexandria" },
108 | { id: 3435907, name: "Buenos Aires" },
109 | { id: 3435910, name: "Buenos Aires" },
110 | { id: 3436943, name: "Santiago" },
111 | { id: 3448433, name: "São Paulo" },
112 | { id: 3448439, name: "São Paulo" },
113 | { id: 3449741, name: "Santiago" },
114 | { id: 3451189, name: "Rio de Janeiro" },
115 | { id: 3451190, name: "Rio de Janeiro" },
116 | { id: 3470127, name: "Belo Horizonte" },
117 | { id: 3491593, name: "Alexandria" },
118 | { id: 3526709, name: "Santiago" },
119 | { id: 3530597, name: "Mexico City" },
120 | { id: 3582383, name: "Chicago" },
121 | { id: 3614583, name: "Buenos Aires" },
122 | { id: 3620603, name: "Buenos Aires" },
123 | { id: 3621520, name: "Santiago" },
124 | { id: 3621524, name: "Santiago" },
125 | { id: 3624593, name: "Buenos Aires" },
126 | { id: 3648559, name: "Barcelona" },
127 | { id: 3668396, name: "Santiago" },
128 | { id: 3668400, name: "Santiago" },
129 | { id: 3675707, name: "Madrid" },
130 | { id: 3688357, name: "Buenos Aires" },
131 | { id: 3688689, name: "Bogotá" },
132 | { id: 3699223, name: "Buenos Aires" },
133 | { id: 3797895, name: "Buenos Aires" },
134 | { id: 3871336, name: "Santiago" },
135 | { id: 3929051, name: "Santiago" },
136 | { id: 3936452, name: "Lima" },
137 | { id: 3936456, name: "Lima" },
138 | { id: 3983671, name: "Santiago" },
139 | { id: 3983689, name: "Santiago" },
140 | { id: 3996933, name: "Madrid" },
141 | { id: 4005539, name: "Guadalajara" },
142 | { id: 4016589, name: "Buenos Aires" },
143 | { id: 4104031, name: "Cairo" },
144 | { id: 4119617, name: "London" },
145 | { id: 4120426, name: "Manila" },
146 | { id: 4125402, name: "Paris" },
147 | { id: 4140963, name: "Washington, D.C." },
148 | { id: 4164138, name: "Miami" },
149 | { id: 4180439, name: "Atlanta" },
150 | { id: 4185632, name: "Cairo" },
151 | { id: 4190598, name: "Dallas" },
152 | { id: 4234985, name: "Cairo" },
153 | { id: 4246659, name: "Paris" },
154 | { id: 4282342, name: "Alexandria" },
155 | { id: 4298960, name: "London" },
156 | { id: 4303602, name: "Paris" },
157 | { id: 4314550, name: "Alexandria" },
158 | { id: 4321929, name: "Delhi" },
159 | { id: 4391354, name: "Houston" },
160 | { id: 4402452, name: "Paris" },
161 | { id: 4430529, name: "Houston" },
162 | { id: 4440906, name: "Philadelphia" },
163 | { id: 4462896, name: "Dallas" },
164 | { id: 4517009, name: "London" },
165 | { id: 4542692, name: "Miami" },
166 | { id: 4559210, name: "Lima" },
167 | { id: 4560349, name: "Philadelphia" },
168 | { id: 4647963, name: "Paris" },
169 | { id: 4671576, name: "Atlanta" },
170 | { id: 4684888, name: "Dallas" },
171 | { id: 4699066, name: "Houston" },
172 | { id: 4717560, name: "Paris" },
173 | { id: 4744091, name: "Alexandria" },
174 | { id: 4829861, name: "Alexandria" },
175 | { id: 4865871, name: "Madrid" },
176 | { id: 4883772, name: "Atlanta" },
177 | { id: 4887398, name: "Chicago" },
178 | { id: 4917537, name: "Alexandria" },
179 | { id: 4974617, name: "Paris" },
180 | { id: 4984500, name: "Atlanta" },
181 | { id: 5016108, name: "Alexandria" },
182 | { id: 5056033, name: "London" },
183 | { id: 5082573, name: "Alexandria" },
184 | { id: 5111056, name: "Cairo" },
185 | { id: 5114824, name: "Delhi" },
186 | { id: 5124411, name: "Lima" },
187 | { id: 5128581, name: "New York City" },
188 | { id: 5131095, name: "Philadelphia" },
189 | { id: 5160783, name: "Lima" },
190 | { id: 5174095, name: "Toronto" },
191 | { id: 5186266, name: "Dallas" },
192 | { id: 5194369, name: "Houston" },
193 | { id: 5202009, name: "Moscow" },
194 | { id: 5225919, name: "Alexandria" },
195 | { id: 5304640, name: "Miami" },
196 | { id: 5342522, name: "Delhi" },
197 | { id: 5367815, name: "London" },
198 | { id: 5368361, name: "Los Angeles" },
199 | { id: 5526233, name: "Miami" },
200 | { id: 5601538, name: "Moscow" },
201 | { id: 5603240, name: "Paris" },
202 | { id: 5717316, name: "Cairo" },
203 | { id: 5722064, name: "Dallas" },
204 | { id: 5777855, name: "Manila" },
205 | { id: 5864312, name: "Houston" },
206 | { id: 5883490, name: "Alexandria" },
207 | { id: 5937522, name: "Delhi" },
208 | { id: 5977783, name: "Houston" },
209 | { id: 6058560, name: "London" },
210 | { id: 6072606, name: "Miami" },
211 | { id: 6167865, name: "Toronto" },
212 | { id: 6321162, name: "Belo Horizonte" },
213 | { id: 6356055, name: "Barcelona" },
214 | { id: 6357912, name: "Guadalajara" },
215 | { id: 6359304, name: "Madrid" },
216 | { id: 6455259, name: "Paris" },
217 | { id: 6559994, name: "Buenos Aires" },
218 | { id: 6643959, name: "Foshan" },
219 | { id: 6942553, name: "Paris" },
220 | { id: 7407364, name: "Dongguan" },
221 | { id: 7661856, name: "Jinan" },
222 | { id: 8010518, name: "Lagos" },
223 | ];
224 |
225 | /** Get a random city from most populated cities. */
226 | export function getRandomCity() {
227 | return cities[Math.floor(Math.random() * cities.length)];
228 | }
229 |
--------------------------------------------------------------------------------
/slack/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/fd36d69fc020470c41cc095183c5c3f2e6a4c00f/slack/demo.png
--------------------------------------------------------------------------------
/slack/install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/fd36d69fc020470c41cc095183c5c3f2e6a4c00f/slack/install.png
--------------------------------------------------------------------------------
/slack/mod.ts:
--------------------------------------------------------------------------------
1 | import {
2 | json,
3 | serve,
4 | validateRequest,
5 | } from "https://deno.land/x/sift@0.4.2/mod.ts";
6 | import { getCardinal } from "https://deno.land/x/cardinal@0.1.0/mod.ts";
7 | import { getRandomCity } from "./cities.ts";
8 |
9 | async function handleRequest(request: Request) {
10 | // validateRequest() ensures that incoming requests are of methods POST and GET.
11 | // Slack sends a POST request with a form field named text that contains the
12 | // information provided by user. We're allowing GET for anyone visiting the
13 | // endpoint in browser. You can disallow GET for your application as it is
14 | // not required by Slack.
15 | const { error } = await validateRequest(request, {
16 | GET: {},
17 | POST: {},
18 | });
19 | if (error) {
20 | // validateRequest() generates appropriate error and status code when
21 | // the request isn't valid. We return that information in a format that's
22 | // appropriate for Slack but there's a good chance that we will not
23 | // encounter this error if the request is actually coming from Slack.
24 | return json(
25 | // "ephemeral" indicates that the response is short-living and is only
26 | // shown to user who invoked the command in Slack.
27 | { response_type: "ephemeral", text: error.message },
28 | { status: error.status },
29 | );
30 | }
31 |
32 | // If a user is trying to visit the endpoint in a browser, let's return a html
33 | // page instructing the user to visit the GitHub page.
34 | if (request.method === "GET") {
35 | return new Response(
36 | `
40 |
41 | Visit GitHub
42 | page for instructions on how to install this Slash Command on your Slack workspace.
43 |
44 | `,
45 | {
46 | headers: {
47 | "content-type": "text/html; charset=UTF-8",
48 | },
49 | },
50 | );
51 | }
52 |
53 | // We use openweathermap.org for weather information and need an API
54 | // token to access their API. The API token is set in Deno Deploy
55 | // dashboard in the "Environment Variables" section, and accessed
56 | // as an environment variable in code.
57 | const token = Deno.env.get("OPEN_WEATHER_TOKEN");
58 | if (!token) {
59 | return json({
60 | response_type: "ephemeral",
61 | text: "Environment variable `OPEN_WEATHER_TOKEN` not set.",
62 | });
63 | }
64 |
65 | try {
66 | const formData = await request.formData();
67 | // The text after command (`/weather `) is passed on to us by Slack in a form
68 | // field of the same name in the request.
69 | if (!formData.has("text")) {
70 | return json(
71 | { response_type: "ephemeral", text: "form field `text` not provided" },
72 | { status: 400 },
73 | );
74 | }
75 |
76 | // We gather location name from `text` field and construct the
77 | // request URL to fetch weather information.
78 | const location = formData.get("text")!.toString().trim();
79 | let url =
80 | `https://api.openweathermap.org/data/2.5/weather?appid=${token}&units=metric`;
81 | if (location) {
82 | url += `&q=${encodeURIComponent(location)}`;
83 | } else {
84 | // If the user didn't provide a city name, we get random city from the list of
85 | // most populated cities.
86 | url += `&id=${getRandomCity().id}`;
87 | }
88 |
89 | const response = await fetch(url);
90 | if (!response.ok) {
91 | // The request might not succeed for several reason but let's just
92 | // return a generic error for simplicity.
93 | return json(
94 | {
95 | response_type: "ephemeral",
96 | text: `Error fetching weather information for \`${location}\`.`,
97 | },
98 | { status: 500 },
99 | );
100 | }
101 |
102 | // Some of the variables name here might not make sense but it is the
103 | // response structure of openweathermaps.org. You can read more about
104 | // the fields at https://openweathermap.org/current#current_JSON.
105 | const {
106 | dt,
107 | name: city,
108 | sys,
109 | main: { humidity, temp, feels_like: feelsLike },
110 | wind: { speed: windSpeed, deg: windDegree },
111 | weather,
112 | visibility,
113 | } = await response.json();
114 |
115 | // We get a short code (SE for South East) for the wind direction
116 | // based on its degree.
117 | const windDirection = getCardinal(windDegree);
118 | // For simplicity, let's just show the time information in
119 | // UTC. The better approach would be to find and use the
120 | // time zone of the location provided by the user.
121 | const timeOptions: Intl.DateTimeFormatOptions = {
122 | timeZone: "UTC",
123 | timeZoneName: "short",
124 | };
125 | const sunrise = new Date(sys.sunrise * 1000).toLocaleTimeString(
126 | "en-US",
127 | timeOptions,
128 | );
129 | const sunset = new Date(sys.sunset * 1000).toLocaleTimeString(
130 | "en-US",
131 | timeOptions,
132 | );
133 | const date = new Date(dt * 1000).toLocaleString("en-US", timeOptions);
134 |
135 | // This is the response that's returned when the command is invoked.
136 | // The layout uses Slack's Block Kit to present information. You
137 | // can learn more about it here: https://api.slack.com/block-kit.
138 | return json({
139 | response_type: "in_channel",
140 | blocks: [
141 | {
142 | type: "section",
143 | text: {
144 | type: "mrkdwn",
145 | text: [
146 | `*${temp}°C*\n`,
147 | `Feels like ${feelsLike}°C. ${weather[0].main}.`,
148 | `Wind: \`${windSpeed} m/s ${windDirection}\``,
149 | `Humidity: \`${humidity}%\` Visibility: \`${
150 | visibility /
151 | 1000
152 | }km\``,
153 | `Sunrise: \`${sunrise}\` Sunset: \`${sunset}\``,
154 | ].join("\n"),
155 | },
156 | accessory: {
157 | type: "image",
158 | image_url: `https://openweathermap.org/img/wn/${
159 | weather[0].icon
160 | }@2x.png`,
161 | alt_text: weather[0].description,
162 | },
163 | },
164 | {
165 | type: "context",
166 | elements: [
167 | {
168 | type: "mrkdwn",
169 | text: `${date}, ${city}, ${sys.country}`,
170 | },
171 | ],
172 | },
173 | ],
174 | });
175 | } catch (error) {
176 | // If something goes wrong in the above block, let's log the error
177 | // and return a generic error to the user.
178 | console.log(error);
179 | return json(
180 | {
181 | response_type: "ephemeral",
182 | text: "Error fetching the results. Please try after sometime.",
183 | },
184 | { status: 500 },
185 | );
186 | }
187 | }
188 |
189 | // Call handleRequest() on requests to "/" path.
190 | serve({
191 | "/": handleRequest,
192 | });
193 |
--------------------------------------------------------------------------------
/slack/readme.md:
--------------------------------------------------------------------------------
1 | # Slack Slash Command Example
2 |
3 | **Weather** - A Slack Slash Command to access weather information.
4 |
5 |
6 |
7 | - [Try Live Version](#try-live-version)
8 | - [Deploy](#deploy)
9 | - [Run Offline](#run-offline)
10 |
11 | ## Try Live Version
12 |
13 | We've a version deployed at https://weather.deno.dev for demo. You can use it to
14 | create the Slash Command in your Slack workspace.
15 |
16 | ### Installation
17 |
18 | Go to https://api.slack.com/apps?new_app and create an app. After successful app
19 | creation, click on "Slash Commands" section and fill out the details as shown
20 | below.
21 |
22 |
23 |
24 | After filling the details click on "Save" button that might at bottom right of
25 | the page. That's it!
26 |
27 | ### Usage
28 |
29 | After the Slack App is setup with the Slash command, run the below command to
30 | get weather information of a place.
31 |
32 | ```
33 | /weather []
34 | ```
35 |
36 | Additionally, you can avoid passing the city argument to get weather information
37 | of a random city from the [list](cities.js).
38 |
39 | ## Deploy
40 |
41 | Follow the steps under [`OpenWeather`](#openweather) section to obtain a
42 | OpenWeather API token and click on the button below to deploy the application.
43 |
44 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/slack/mod.ts&env=OPEN_WEATHER_TOKEN)
45 |
46 | ### OpenWeather
47 |
48 | We use OpenWeather API to obtain weather information.
49 |
50 | Here are the steps to obtain a token to communicate with the API:
51 |
52 | 1. Go to https://home.openweathermap.org/api_keys (Login or Sign Up if required)
53 | 2. Name the key under **Create Key** and click on **Generate**
54 |
55 | That's it.
56 |
57 | ## Run Offline
58 |
59 | You can run the application on your local machine using
60 | [`deno`](https://github.com/denoland/deno).
61 |
62 | ```
63 | OPEN_WEATHER_TOKEN= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/slack/mod.ts
64 | ```
65 |
66 | Grab a token at https://openweathermap.org and set the value for the variable.
67 |
68 | To be able to use the local version on your Slack workspace, you need to use a
69 | tool like [ngrok](https://ngrok.com) to tunnel Slack requests to the app running
70 | on your machine.
71 |
72 | 1. Run `ngrok http 8000` (assuming that the application is running on port
73 | `8000`)
74 | 2. Follow the steps under Installation section, but use the https URL output by
75 | ngrok for **Request URL** field.
76 |
77 | That's it.
78 |
--------------------------------------------------------------------------------
/telegram/README.md:
--------------------------------------------------------------------------------
1 | # Telegram Bot Command
2 |
3 | A simple Telegram Bot Command.
4 |
5 | ## Tutorial
6 |
7 | 1. Follow the
8 | [official Telegram guide](https://core.telegram.org/bots#3-how-do-i-create-a-bot)
9 | for creating a Bot.
10 | 2. Deploy the Bot by clicking on this button:
11 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/mod.ts&env=TOKEN,BOT_NAME)
12 | 3. Input `TOKEN` and `BOT_NAME` env variable fields. The token value should be
13 | available from the BotFather and the value `BOT_NAME` is the bot username
14 | that ends with either `_bot` or `Bot`.
15 | 4. Click on **Create** to create the project, then on **Deploy** to deploy the
16 | script.
17 | 5. Grab the URL that's displayed under Domains in the Production Deployment
18 | card.
19 | 6. Visit the following URL (make sure to replace the template fields):
20 | ```
21 | https://api.telegram.org/bot/setWebhook?url=/
22 | ```
23 |
24 | > Replace with the token from the BotFather and ``
25 | > with the URL from the previous step.
26 |
27 | 7. Add a command to the bot by visiting the following URL:
28 |
29 | ```
30 | https://api.telegram.org/bot/setMyCommands?commands=[{"command":"ping","description":"Should return a 'pong' from the Bot."}]
31 | ```
32 | 8. Now you can invite the bot to a Group Chat or just PM the bot with the
33 | following command "/ping".
34 |
35 |
36 |
37 | ## Run Offline
38 |
39 | You can run the example program on your machine using
40 | [`deno`](https://github.com/denoland/deno):
41 |
42 | ```sh
43 | TOKEN= BOT_NAME= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/mod.ts
44 | ```
45 |
46 | You need to use a tool like [ngrok](https://ngrok.com) to tunnel Telegram
47 | requests to the app running on your machine.
48 |
49 | 1. Run `ngrok http 8080` (assuming that the application is running on port
50 | `8080`)
51 | 2. While registering the bot, use the https URL output by ngrok for **url**
52 | query.
53 |
54 | > Example:
55 | > `https://api.telegram.org/bot/setWebhook?url=/`
56 |
57 | That's it.
58 |
--------------------------------------------------------------------------------
/telegram/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/fd36d69fc020470c41cc095183c5c3f2e6a4c00f/telegram/demo.png
--------------------------------------------------------------------------------
/telegram/mod.ts:
--------------------------------------------------------------------------------
1 | import {
2 | json,
3 | PathParams,
4 | serve,
5 | validateRequest,
6 | } from "https://deno.land/x/sift@0.4.2/mod.ts";
7 |
8 | // For all requests to "/" endpoint, we want to invoke handleTelegram() handler.
9 | // Recommend using a secret path in the URL, e.g. https://www.example.com/.
10 | serve({
11 | "/": () => new Response("Welcome to the Telegram Bot site."),
12 | "/:slug": handleTelegram,
13 | });
14 |
15 | // The main logic of the Telegram bot is defined in this function.
16 | async function handleTelegram(request: Request, params?: PathParams) {
17 | // Gets the environment variable TOKEN
18 | const TOKEN = Deno.env.get("TOKEN")!;
19 | // Gets the environment variable BOT_NAME
20 | const BOT_NAME = Deno.env.get("BOT_NAME")!;
21 |
22 | // If the environment variable TOKEN is not found, throw an error.
23 | if (!TOKEN) {
24 | throw new Error("environment variable TOKEN is not set");
25 | }
26 |
27 | // For using a secret path in the URL, e.g. https://www.example.com/. If wrong url return "invalid request".
28 | if (params?.slug != TOKEN) {
29 | return json(
30 | { error: "invalid request" },
31 | {
32 | status: 401,
33 | },
34 | );
35 | }
36 |
37 | // Make sure the request is a POST request.
38 | const { error } = await validateRequest(request, {
39 | POST: {},
40 | });
41 |
42 | // validateRequest populates the error if the request doesn't meet
43 | // the schema we defined.
44 | if (error) {
45 | return json({ error: error.message }, { status: error.status });
46 | }
47 |
48 | // Get the body of the request
49 | const body = await request.text();
50 | // Parse the raw JSON body from Telegrams webhook.
51 | const data = await JSON.parse(body);
52 |
53 | // Check if the method is a POST request and that there was somthing in the body.
54 | if (request.method === "POST") {
55 | // Cheack if the command was "/ping".
56 | if (
57 | data && data["message"] && data["message"]["text"] &&
58 | (data["message"]["text"].toLowerCase() == "/ping" ||
59 | data["message"]["text"].toLowerCase() ==
60 | "/ping@" + BOT_NAME.toLowerCase())
61 | ) {
62 | // Store the chat id of the Group Chat, Channel or PM.
63 | const chatId: number = data["message"]["chat"]["id"];
64 |
65 | // Calls the API service to Telegram for sending a message.
66 | const { dataTelegram, errors } = await sendMessage(
67 | chatId,
68 | "Pong",
69 | TOKEN,
70 | );
71 |
72 | if (errors) {
73 | console.error(errors.map((error) => error.message).join("\n"));
74 | return json({ error: "couldn't create the message" }, {
75 | status: 500,
76 | });
77 | }
78 |
79 | // Returns the answer and set status code 201.
80 | return json({ dataTelegram }, { status: 201 });
81 | }
82 | // Returns empty object and set status code 200.
83 | return json({}, { status: 200 });
84 | }
85 |
86 | // We will return a bad request error as a valid Telegram request
87 | // shouldn't reach here.
88 | return json({ error: "bad request" }, { status: 400 });
89 | }
90 |
91 | /** What to store for an error message. */
92 | type TelegramError = {
93 | message?: string;
94 | };
95 |
96 | /** Sending a POST request to Telegram's API to send a message. */
97 | async function sendMessage(
98 | chatId: number,
99 | text: string,
100 | token: string,
101 | ): Promise<{
102 | dataTelegram?: unknown;
103 | errors?: TelegramError[];
104 | }> {
105 | try {
106 | const res = await fetch(
107 | `https://api.telegram.org/bot${token}/sendMessage`,
108 | {
109 | method: "POST",
110 | headers: {
111 | "content-type": "application/json",
112 | },
113 | body: JSON.stringify({
114 | chat_id: chatId,
115 | text: text,
116 | }),
117 | },
118 | );
119 | const { dataTelegram, errors } = await res.json();
120 |
121 | if (errors) {
122 | return { dataTelegram, errors };
123 | }
124 |
125 | return { dataTelegram };
126 | } catch (error) {
127 | console.error(error);
128 | return { errors: [{ message: "failed to fetch data from Telegram" }] };
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/yaus/mod.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | import {
3 | h,
4 | jsx,
5 | PathParams,
6 | serve,
7 | } from "https://deno.land/x/sift@0.4.2/mod.ts";
8 | import { nanoid } from "https://cdn.esm.sh/v14/nanoid@3.1.20/esnext/nanoid.js";
9 |
10 | serve({
11 | "/": homePage,
12 | "/:code": handleCodeRequests,
13 | });
14 |
15 | // Styles for the home page.
16 | const style = css`
17 | @import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
18 | body {
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | min-height: 100vh;
23 | font-family: "Roboto", sans-serif;
24 | background-color: #f8e3c6ad;
25 | margin: 0;
26 | }
27 |
28 | header {
29 | display: flex;
30 | align-items: center;
31 | justify-content: space-between;
32 | padding-top: 0.5em;
33 | width: 70vw;
34 | }
35 |
36 | .brand {
37 | font-size: 2rem;
38 | color: rgba(0, 0, 0, 0.8);
39 | }
40 |
41 | main {
42 | flex: 1;
43 | display: flex;
44 | align-items: center;
45 | justify-content: center;
46 | flex-direction: column;
47 | padding-bottom: 8em;
48 | }
49 |
50 | form {
51 | display: flex;
52 | justify-content: center;
53 | margin-block-end: 0;
54 | }
55 |
56 | form input {
57 | font-size: 1.5rem;
58 | border: 0.2em black solid;
59 | border-radius: 0.4em;
60 | padding: 0.5rem;
61 | width: 20em;
62 | background-color: transparent;
63 | }
64 |
65 | form input:focus {
66 | outline: none;
67 | }
68 |
69 | form button[type="submit"] {
70 | font-size: 1.5em;
71 | border: 0.2em black solid;
72 | border-radius: 0.4em;
73 | margin-left: 0.4em;
74 | background-color: transparent;
75 | }
76 |
77 | .link {
78 | display: flex;
79 | border: 0.2em solid black;
80 | padding: 0.2em 0.1em 0.2em 0.4em;
81 | font-size: 1.5em;
82 | margin-top: 1.5em;
83 | align-items: center;
84 | border-radius: 0.4em;
85 | background-color: transparent;
86 | }
87 |
88 | #clipboard {
89 | padding: 0;
90 | margin-left: 1em;
91 | background-color: transparent;
92 | border-style: none;
93 | }
94 |
95 | @media all and (max-width: 40em) {
96 | .brand {
97 | font-size: 1.4em;
98 | }
99 |
100 | header {
101 | width: 94vw;
102 | }
103 |
104 | form {
105 | flex-direction: column;
106 | }
107 |
108 | form input {
109 | width: 12.5em;
110 | }
111 |
112 | form button[type="submit"] {
113 | margin: 0.4em 0 0 0;
114 | }
115 |
116 | .link {
117 | font-size: 1.2em;
118 | }
119 |
120 | footer {
121 | font-size: small;
122 | }
123 | }
124 | `;
125 |
126 | // Script for the clipboard button.
127 | const script = `
128 | document.addEventListener("click", async (event) => {
129 | if (navigator?.clipboard && event.target.parentNode.id === "clipboard") {
130 | try {
131 | const text = event.target.parentNode.previousSibling.innerText;
132 | await navigator.clipboard.writeText(text);
133 | } catch (error) {
134 | console.error(error);
135 | }
136 | }
137 | });
138 | `;
139 |
140 | /** The main function that responds to `/` route. */
141 | async function homePage(request: Request) {
142 | let shortCode;
143 |
144 | // The input form makes a GET request with 'url' query
145 | // populated when someone submits the form. We use this
146 | // information to either create a new short link or get
147 | // an existing short link for the url.
148 | const { protocol, host, searchParams } = new URL(request.url);
149 | const url = searchParams.get("url");
150 | if (url) {
151 | let code = await findCode(url);
152 | if (code) {
153 | shortCode = code;
154 | } else {
155 | code = searchParams.get("shortCode") ?? nanoid(6);
156 | shortCode = (await addUrl(url, code)).code;
157 | }
158 | }
159 |
160 | return jsx(
161 |
162 |
163 |
164 |
165 | YAUS | Yet Another URL Shortener
166 |
167 |
171 |
172 |
173 |
187 |
188 |
200 | {shortCode && (
201 |
202 |
{`${protocol}//${host}/${shortCode}`}
203 |
204 |
209 |
210 |
211 | )}
212 |
213 |
216 |
217 | ,
218 | );
219 | }
220 |
221 | /** Handle short link (`/`) requests. */
222 | async function handleCodeRequests(_request: Request, params?: PathParams) {
223 | const { code = "" } = params as { code: string };
224 | if (code) {
225 | const url = await findUrl(code);
226 | if (url) {
227 | return Response.redirect(url, 302);
228 | }
229 | }
230 |
231 | return jsx(url not found, { status: 404 });
232 | }
233 |
234 | /** Cache the code as key and url as value. */
235 | const codeCache = new Map();
236 | /** Cache the url as key and code as value. */
237 | const urlCache = new Map();
238 |
239 | // GraphQL to find url by code.
240 | const findUrlGql = gql`
241 | query($code: String!) {
242 | findUrlByCode(code: $code) {
243 | url
244 | }
245 | }
246 | `;
247 | /** Find url for the provided url. */
248 | async function findUrl(code: string): Promise {
249 | if (codeCache.has(code)) {
250 | return codeCache.get(code);
251 | }
252 |
253 | const { data } = (await executeFauna(findUrlGql, { code })) as {
254 | data: { findUrlByCode: { url: string } };
255 | };
256 | if (data?.findUrlByCode?.url) {
257 | codeCache.set(code, data?.findUrlByCode.url);
258 | return data?.findUrlByCode.url;
259 | }
260 | }
261 |
262 | // GraphQL to find short code by url.
263 | const findCodeGql = gql`
264 | query($url: String!) {
265 | findCodeByUrl(url: $url) {
266 | code
267 | }
268 | }
269 | `;
270 | /** Find short code for the provided url. */
271 | async function findCode(url: string): Promise {
272 | if (urlCache.has(url)) {
273 | return urlCache.get(url);
274 | }
275 |
276 | const { data } = (await executeFauna(findCodeGql, { url })) as {
277 | data: { findCodeByUrl: { code: string } };
278 | };
279 | if (data?.findCodeByUrl?.code) {
280 | urlCache.set(url, data.findCodeByUrl.code);
281 | return data.findCodeByUrl.code;
282 | }
283 | }
284 |
285 | // GraphQL to create a new link.
286 | const addLinkGQL = gql`
287 | mutation($url: String!, $code: String!) {
288 | createLink(data: { url: $url, code: $code }) {
289 | code
290 | url
291 | }
292 | }
293 | `;
294 | /** Create a new link with the provided url and code.
295 | * Also populate the cache. */
296 | async function addUrl(
297 | url: string,
298 | code: string,
299 | ): Promise<{ code: string; url: string }> {
300 | const {
301 | data: { createLink: link },
302 | } = (await executeFauna(addLinkGQL, { url, code })) as {
303 | data: { createLink: { url: string; code: string } };
304 | };
305 |
306 | codeCache.set(code, link?.url);
307 | urlCache.set(url, link?.code);
308 | return link;
309 | }
310 |
311 | /** Generic function to execute GraphQL at the Fauna GraphQL endpoint. */
312 | async function executeFauna(
313 | query: string,
314 | variables: { [key: string]: unknown },
315 | ): Promise<{
316 | data?: unknown;
317 | error?: { message: string };
318 | }> {
319 | const token = Deno.env.get("FAUNA_SECRET");
320 | if (!token) {
321 | throw new Error("environment variable FAUNA_SECRET not set");
322 | }
323 |
324 | try {
325 | const res = await fetch("https://graphql.fauna.com/graphql", {
326 | method: "POST",
327 | headers: {
328 | authorization: `Bearer ${token}`,
329 | "content-type": "application/json",
330 | },
331 | body: JSON.stringify({
332 | query,
333 | variables,
334 | }),
335 | });
336 |
337 | const { data, errors } = await res.json();
338 | if (errors) {
339 | return { data, error: errors[0] };
340 | }
341 |
342 | return { data };
343 | } catch (error) {
344 | return { error };
345 | }
346 | }
347 |
348 | /** Wrapper function to get syntax highlight for GraphQL in editors. */
349 | function gql(gql: TemplateStringsArray) {
350 | return gql.join("");
351 | }
352 |
353 | /** Wrapper function to get syntax highlight for CSS in editors. */
354 | function css(style: TemplateStringsArray) {
355 | return style.join("");
356 | }
357 |
--------------------------------------------------------------------------------
/yaus/readme.md:
--------------------------------------------------------------------------------
1 | # Yet Another URL Shortener (YAUS)
2 |
3 | A URL shortener built on top of Deno Deploy and FaunaDB.
4 |
5 | Visit [`https://yaus.deno.dev`](https://yaus.deno.dev) for a live version.
6 |
7 | - [Deploy](#deploy)
8 | - [Run Offline](#run-offline)
9 |
10 | ## Deploy
11 |
12 | Follow the steps under [`Fauna`](#fauna) section to obtain a FaunaDB secret and
13 | click on the button below to deploy the application.
14 |
15 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/yaus/mod.tsx&env=FAUNA_SECRET)
16 |
17 | ### Fauna
18 |
19 | We use FaunaDB to store our application data. Follow the below steps to create a
20 | database and obtain a secret to access the DB from your Deno Deploy application.
21 |
22 | Create a new database:
23 |
24 | 1. Go to https://dashboard.fauna.com (login if required) and click on **New
25 | Database**
26 | 2. Fill the **Database Name** field and click on **Save**.
27 | 3. Click on **GraphQL** section visible on the left sidebar.
28 | 4. Download [`schema.gql`](schema.gql) to your local machine and import the
29 | file.
30 |
31 | Generate a secret to access the database:
32 |
33 | 1. Click on **Security** section and click on **New Key**.
34 | 2. Select **Server** role and click on **Save**. Copy the secret.
35 |
36 | ## Run Offline
37 |
38 | You can run the application on your local machine using
39 | [`deno`](https://github.com/denoland/deno).
40 |
41 | ```
42 | FAUNA_SECRET= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/yaus/mod.tsx
43 | ```
44 |
45 | Replace `` with your FaunaDB secret.
46 |
--------------------------------------------------------------------------------
/yaus/schema.gql:
--------------------------------------------------------------------------------
1 | # An object containing the long url and corresponding short code.
2 | type Link {
3 | url: String! @unique
4 | code: String! @unique
5 | }
6 |
7 | # Queries to fetch code by url and vice versa.
8 | type Query {
9 | findCodeByUrl(url: String!): Link
10 | findUrlByCode(code: String!): Link
11 | }
12 |
--------------------------------------------------------------------------------