Repository: 8398a7/action-slack (ts)

Issue:
Compact format like travis
> Ok, sad, but if you could add the time the job took and the compact (travis like) formatting I would be very happy.

Pull request:
Issue/70
close #70

Patch:
diff --git a/.github/workflows/slack-mainline.yml b/.github/workflows/slack-mainline.yml
index 401ef7a..c487132 100644
--- a/.github/workflows/slack-mainline.yml
+++ b/.github/workflows/slack-mainline.yml
@@ -80,6 +80,23 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_FOR_INTEGRATION_TEST }}
+      - name: Custom Field2
+        uses: 8398a7/action-slack@pre
+        with:
+          status: custom
+          fields: all
+          custom_payload: |
+            {
+              username: 'action-slack',
+              icon_emoji: ':octocat:',
+              attachments: [{
+                color: 'good',
+                text: `${process.env.AS_WORKFLOW}\n${process.env.AS_JOB} (${process.env.AS_COMMIT}) of ${process.env.AS_REPO}@master by ${process.env.AS_AUTHOR} succeeded in ${process.env.AS_TOOK}`,
+              }]
+            }
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_LEGACY_WEBHOOK_URL_FOR_INTEGRATION_TEST }}
       - name: Fields Check
         uses: 8398a7/action-slack@v3
         with:
diff --git a/.github/workflows/slack-pre.yml b/.github/workflows/slack-pre.yml
index 2f0b6ce..2096019 100644
--- a/.github/workflows/slack-pre.yml
+++ b/.github/workflows/slack-pre.yml
@@ -73,6 +73,23 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_FOR_INTEGRATION_TEST }}
+      - name: Custom Field2
+        uses: 8398a7/action-slack@pre
+        with:
+          status: custom
+          fields: all
+          custom_payload: |
+            {
+              username: 'action-slack',
+              icon_emoji: ':octocat:',
+              attachments: [{
+                color: 'good',
+                text: `${process.env.AS_WORKFLOW}\n${process.env.AS_JOB} (${process.env.AS_COMMIT}) of ${process.env.AS_REPO}@master by ${process.env.AS_AUTHOR} succeeded in ${process.env.AS_TOOK}`,
+              }]
+            }
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_LEGACY_WEBHOOK_URL_FOR_INTEGRATION_TEST }}
       - name: No GITHUB_TOKEN
         uses: 8398a7/action-slack@pre
         with:
diff --git a/README.md b/README.md
index 09e4b8d..0cf2949 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@
 
 - [Document](https://action-slack.netlify.com)
 
+## Quick Start
+
+You can learn more about it [here](https://action-slack.netlify.com/usecase/01-general).
+
 ```yaml
 steps:
   - uses: 8398a7/action-slack@v3
@@ -22,3 +26,29 @@ steps:
 ```
 
 <img width="495" alt="success" src="https://user-images.githubusercontent.com/8043276/84587112-64844800-ae57-11ea-8007-7ce83a91dae3.png" />
+
+## Custom Formats of your choice
+
+You can learn more about it [here](https://action-slack.netlify.com/usecase/02-custom).
+
+```yaml
+steps:
+  - uses: 8398a7/action-slack@v3
+  with:
+    status: custom
+    fields: all
+    custom_payload: |
+      {
+        username: 'action-slack',
+        icon_emoji: ':octocat:',
+        attachments: [{
+          color: 'good',
+          text: `${process.env.AS_WORKFLOW}\n${process.env.AS_JOB} (${process.env.AS_COMMIT}) of ${process.env.AS_REPO}@master by ${process.env.AS_AUTHOR} succeeded in ${process.env.AS_TOOK}`,
+        }]
+      }
+  env:
+    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_LEGACY_WEBHOOK_URL_FOR_INTEGRATION_TEST }}
+```
+
+<img width="633" alt="custom" src="https://user-images.githubusercontent.com/8043276/85947865-3723b800-b988-11ea-80f7-6db5329c6af7.png">
diff --git a/docs/config.js b/docs/config.js
index 03438ed..10b7a7f 100644
--- a/docs/config.js
+++ b/docs/config.js
@@ -23,11 +23,8 @@ const config = {
     },
   },
   sidebar: {
-    forcedNavOrder: ['/usage', '/with', '/migration', '/usecase', '/contributors'],
-    collapsedNav: [
-      '/usecase', // add trailing slash if enabled above
-      '/migration',
-    ],
+    forcedNavOrder: ['/usage', '/with', '/fields', '/migration', '/usecase', '/contributors'],
+    collapsedNav: ['/usecase', '/migration'],
     links: [{ text: 'GitHub', link: 'https://github.com/8398a7/action-slack' }],
     frontline: false,
     ignoreIndex: true,
diff --git a/docs/content/fields.md b/docs/content/fields.md
new file mode 100644
index 0000000..6ab60e7
--- /dev/null
+++ b/docs/content/fields.md
@@ -0,0 +1,44 @@
+---
+title: Fields
+metaTitle: Fields | action-slack
+metaDescription: This explains the values that can be specified in Fields.
+---
+
+If you have more than one, please enter it in csv format.  
+Corresponding types are as follows.
+
+<img width="495" alt="success" src="https://user-images.githubusercontent.com/8043276/84587112-64844800-ae57-11ea-8007-7ce83a91dae3.png" />
+
+| Field     | Required `GITHUB_TOKEN` | Environment Variable | Description                           |
+| --------- | ----------------------- | -------------------- | ------------------------------------- |
+| repo      | no                      | `AS_REPO`            | A working repository name             |
+| commit    | no                      | `AS_COMMIT`          | commit hash                           |
+| eventName | no                      | `AS_EVENT_NAME`      | trigger event name                    |
+| ref       | no                      | `AS_REF`             | git refrence                          |
+| workflow  | no                      | `AS_WORKFLOW`        | GitHub Actions workflow name          |
+| message   | yes                     | `AS_MESSAGE`         | commit message                        |
+| author    | yes                     | `AS_AUTHOR`          | The author who pushed                 |
+| job       | yes                     | `AS_JOB`             | The name of the job that was executed |
+| took      | yes                     | `AS_TOOK`            | Execution time for the job            |
+
+```yaml
+steps:
+  - uses: 8398a7/action-slack@v3
+    with:
+      fields: repo,commit
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # optional
+      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
+```
+
+If you want all items, specify `all`.
+
+```yaml
+steps:
+  - uses: 8398a7/action-slack@v3
+    with:
+      fields: all
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # optional
+      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
+```
diff --git a/docs/content/usecase.md b/docs/content/usecase.md
index 4ce99fe..18f4a1c 100644
--- a/docs/content/usecase.md
+++ b/docs/content/usecase.md
@@ -1,7 +1,10 @@
 ---
-title: Usecase
-metaTitle: Usecase | action-slack
+title: Use case
+metaTitle: Use case | action-slack
 metaDescription: This describes the usecase of action-slack.
 ---
 
-Usecase
+This page introduces a use case using action-slack.
+
+- [General use case](/usecase/01-general)
+- [Custom use case](/usecase/02-custom)
diff --git a/docs/content/usecase/01-basic-usecase.md b/docs/content/usecase/01-basic-usecase.md
deleted file mode 100644
index ddec591..0000000
--- a/docs/content/usecase/01-basic-usecase.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Basic Usecase
-metaTitle: Basic Usecase | action-slack
-metaDescription: This describes the basic usecase of action-slack.
----
-
-basic
diff --git a/docs/content/usecase/01-general.md b/docs/content/usecase/01-general.md
new file mode 100644
index 0000000..f8d1a65
--- /dev/null
+++ b/docs/content/usecase/01-general.md
@@ -0,0 +1,24 @@
+---
+title: General use case
+metaTitle: General use case | action-slack
+metaDescription: This describes the general use case of action-slack.
+---
+
+Notify slack of the results of a single job run.
+
+```yaml
+steps:
+  - uses: 8398a7/action-slack@v3
+    with:
+      status: ${{ job.status }}
+      fields: repo,message,commit,author,action,eventName,ref,workflow,job,took # selectable (default: repo,message)
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # optional
+      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
+    if: always() # Pick up events even if the job fails or is canceled.
+```
+
+`status: ${{ job.status }}` allows a job to succeed, fail or cancel etc. to action-slack.  
+`if: always()` to trigger action-slack even if the job fails Let them.
+
+For the fields, look at [Fields](/fields) to determine what you want.
diff --git a/docs/content/usecase/02-custom.md b/docs/content/usecase/02-custom.md
new file mode 100644
index 0000000..666646b
--- /dev/null
+++ b/docs/content/usecase/02-custom.md
@@ -0,0 +1,79 @@
+---
+title: Custom use case
+metaTitle: Custom use case | action-slack
+metaDescription: This describes the custom use case of action-slack.
+---
+
+You will often want to send notifications in a format other than the one that action-slack has determined.  
+In such a case, consider using `type: custom`.
+
+If you specify a payload in accordance with the slack specification, action-slack will notify you as it is.  
+Use `status: cutom` and [custom_payload](/with#custom_payload) to customize notifications to your liking can be sent.
+
+```yaml
+steps:
+  - uses: 8398a7/action-slack@v3
+    with:
+      status: custom
+      custom_payload: |
+        {
+          text: "Custom Field Check",
+          attachments: [{
+            "author_name": "8398a7@action-slack", // json
+            fallback: 'fallback',
+            color: 'good',
+            title: 'CI Result',
+            text: 'Succeeded',
+            fields: [{
+              title: 'lower case',
+              value: 'LOWER CASE CHECK'.toLowerCase(),
+              short: true
+            },
+            {
+              title: 'reverse',
+              value: 'gnirts esrever'.split('').reverse().join(''),
+              short: true
+            },
+            {
+              title: 'long title1',
+              value: 'long value1',
+              short: false
+            }],
+            actions: [{
+            }]
+          }]
+        }
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # optional
+      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
+```
+
+As you can see, the JavaScript functionality is available in the custom_payload . (e.g. `toLowerCase()`)  
+We have even more options for those who want to use custom notifications, but want to use the Fields feature.
+
+```yaml
+steps:
+  - uses: 8398a7/action-slack@v3
+    with:
+      status: custom
+      fields: all
+      custom_payload: |
+        {
+          username: 'action-slack',
+          icon_emoji: ':octocat:',
+          attachments: [{
+            color: 'good',
+            text: `${process.env.AS_WORKFLOW}\n${process.env.AS_JOB} (${process.env.AS_COMMIT}) of ${process.env.AS_REPO}@master by ${process.env.AS_AUTHOR} succeeded in ${process.env.AS_TOOK}`,
+          }]
+        }
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_LEGACY_WEBHOOK_URL_FOR_INTEGRATION_TEST }}
+```
+
+You can access the values retrieved by Fields through environment variables.  
+See [Fields](/fields) for the available environment variables.
+
+<img width="633" alt="custom" src="https://user-images.githubusercontent.com/8043276/85947865-3723b800-b988-11ea-80f7-6db5329c6af7.png" />
+
+If there's a good format, I'd like to introduce it on this page from time to time.
diff --git a/docs/content/with.md b/docs/content/with.md
index b79ff9d..b2341cf 100644
--- a/docs/content/with.md
+++ b/docs/content/with.md
@@ -8,19 +8,19 @@ This page describes the elements that can be specified in with.
 
 # List
 
-|key|value|default|
-|---|---|---|
-|[status](/with#status)|`'success'` or `'failure'` or `'cancelled'` or `'custom'`|`''`|
-|[fields](/with#fields)|You can choose the items you want to add to the fields at the time of notification.|`'repo,commit'`|
-|[text](/with#text)|Specify the text you want to add.|`''`|
-|[author_name](/with#author_name)|It can be overwritten by specifying. The job name is recommend.|`'8398a7@action-slack'`|
-|[mention](/with#mention)|`'here'` or `'channel'` or [user_group_id](https://api.slack.com/reference/surfaces/formatting#mentioning-groups) or [user_id](https://api.slack.com/reference/surfaces/formatting#mentioning-users)|`''`|
-|[if_mention](/with#mention)|Specify `'success'` or `'failure'` or `'cancelled'` or `'custom'` or `'always'`.|`''`|
-|[username](/with#username)|Override the legacy integration's default name.|`''`|
-|[icon_emoji](/with#icon_emoji)|[emoji code](https://www.webfx.com/tools/emoji-cheat-sheet/) string to use in place of the default icon.|`''`|
-|[icon_url](/with#icon_url)|icon image URL string to use in place of the default icon.|`''`|
-|[channel](/with#channel)|Override the legacy integration's default channel. This should be an ID, such as `C8UJ12P4P`.|`''`|
-|[custom_payload](/with#custom_payload)|e.g. `{"text": "Custom Field Check", obj: 'LOWER CASE'.toLowerCase()}`|`''`|
+| key                                    | value                                                                                                                                                                                                | default                 |
+| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
+| [status](/with#status)                 | `'success'` or `'failure'` or `'cancelled'` or `'custom'`                                                                                                                                            | `''`                    |
+| [fields](/fields)                      | You can choose the items you want to add to the fields at the time of notification.                                                                                                                  | `'repo,commit'`         |
+| [text](/with#text)                     | Specify the text you want to add.                                                                                                                                                                    | `''`                    |
+| [author_name](/with#author_name)       | It can be overwritten by specifying. The job name is recommend.                                                                                                                                      | `'8398a7@action-slack'` |
+| [mention](/with#mention)               | `'here'` or `'channel'` or [user_group_id](https://api.slack.com/reference/surfaces/formatting#mentioning-groups) or [user_id](https://api.slack.com/reference/surfaces/formatting#mentioning-users) | `''`                    |
+| [if_mention](/with#mention)            | Specify `'success'` or `'failure'` or `'cancelled'` or `'custom'` or `'always'`.                                                                                                                     | `''`                    |
+| [username](/with#username)             | Override the legacy integration's default name.                                                                                                                                                      | `''`                    |
+| [icon_emoji](/with#icon_emoji)         | [emoji code](https://www.webfx.com/tools/emoji-cheat-sheet/) string to use in place of the default icon.                                                                                             | `''`                    |
+| [icon_url](/with#icon_url)             | icon image URL string to use in place of the default icon.                                                                                                                                           | `''`                    |
+| [channel](/with#channel)               | Override the legacy integration's default channel. This should be an ID, such as `C8UJ12P4P`.                                                                                                        | `''`                    |
+| [custom_payload](/with#custom_payload) | e.g. `{"text": "Custom Field Check", obj: 'LOWER CASE'.toLowerCase()}`                                                                                                                               | `''`                    |
 
 # status
 
@@ -36,35 +36,6 @@ steps:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
 ```
 
-# fields
-
-If you have more than one, please enter it in csv format.  
-Corresponding types are as follows.
-
-- repo
-- commit
-- message
-  - required GITHUB_TOKEN
-- author
-  - required GITHUB_TOKEN
-- job
-  - required GITHUB_TOKEN
-- took
-  - required GITHUB_TOKEN
-- eventName
-- ref
-- workflow
-
-```yaml
-steps:
-  - uses: 8398a7/action-slack@v3
-    with:
-      fields: repo,commit
-    env:
-      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # optional
-      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
-```
-
 # text
 
 e.g.
diff --git a/src/client.ts b/src/client.ts
index aca4909..bfc2390 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -2,6 +2,7 @@ import * as core from '@actions/core';
 import { context, getOctokit } from '@actions/github';
 import { GitHub } from '@actions/github/lib/utils';
 import { IncomingWebhook, IncomingWebhookSendArguments } from '@slack/webhook';
+import { FieldFactory } from './fields';
 
 export const Success = 'success';
 type SuccessType = 'success';
@@ -13,6 +14,8 @@ export const Custom = 'custom';
 export const Always = 'always';
 type AlwaysType = 'always';
 
+export type GitHub = InstanceType<typeof GitHub>;
+
 export interface With {
   status: string;
   mention: string;
@@ -35,8 +38,9 @@ const groupMention = ['here', 'channel'];
 const subteamMention = 'subteam^';
 
 export class Client {
+  private fieldFactory: FieldFactory;
   private webhook: IncomingWebhook;
-  private github?: InstanceType<typeof GitHub>;
+  private github?: GitHub;
   private with: With;
 
   constructor(props: With, token?: string, webhookUrl?: string) {
@@ -51,6 +55,7 @@ export class Client {
       throw new Error('Specify secrets.SLACK_WEBHOOK_URL');
     }
     this.webhook = new IncomingWebhook(webhookUrl);
+    this.fieldFactory = new FieldFactory(this.with.fields, this.github);
   }
 
   async success(text: string) {
@@ -89,28 +94,20 @@ export class Client {
     return template;
   }
 
+  async custom(payload: string) {
+    await this.fieldFactory.attachments();
+    /* eslint-disable no-var */
+    var template: IncomingWebhookSendArguments = eval(`template = ${payload}`);
+    /* eslint-enable */
+    return template;
+  }
+
   async send(payload: string | IncomingWebhookSendArguments) {
     core.debug(JSON.stringify(context, null, 2));
     await this.webhook.send(payload);
     core.debug('send message');
   }
 
-  includesField(field: string) {
-    const { fields } = this.with;
-    const normalized = fields.replace(/ /g, '').split(',');
-    return normalized.includes(field);
-  }
-
-  filterField<T extends Array<Field | undefined>, U extends undefined>(
-    array: T,
-    diff: U,
-  ) {
-    return array.filter(item => item !== diff) as Exclude<
-      T extends { [K in keyof T]: infer U } ? U : never,
-      U
-    >[];
-  }
-
   private async payloadTemplate() {
     const text = '';
     const { username, icon_emoji, icon_url, channel } = this.with;
@@ -125,164 +122,12 @@ export class Client {
         {
           color: '',
           author_name: this.with.author_name,
-          fields: await this.fields(),
+          fields: await this.fieldFactory.attachments(),
         },
       ],
     };
   }
 
-  private async fields(): Promise<Field[]> {
-    const { sha } = context;
-    const { owner, repo } = context.repo;
-
-    const commit = await this.github?.repos.getCommit({
-      owner,
-      repo,
-      ref: sha,
-    });
-    const author = commit?.data.commit.author;
-
-    return this.filterField(
-      [
-        this.repo,
-        commit && this.includesField('message')
-          ? {
-              title: 'message',
-              value: `<${commit.data.html_url}|${
-                commit.data.commit.message.split('\n')[0]
-              }>`,
-              short: true,
-            }
-          : undefined,
-        this.commit,
-        author && this.includesField('author')
-          ? {
-              title: 'author',
-              value: `${author.name}<${author.email}>`,
-              short: true,
-            }
-          : undefined,
-        await this.job(),
-        await this.took(),
-        this.eventName,
-        this.ref,
-        this.workflow,
-      ],
-      undefined,
-    );
-  }
-
-  private async took(): Promise<Field | undefined> {
-    if (!this.includesField('took')) return undefined;
-
-    const { owner, repo } = context.repo;
-    const resp = await this.github?.actions.listJobsForWorkflowRun({
-      owner,
-      repo,
-      run_id: context.runId,
-    });
-    const currentJob = resp?.data.jobs.find(job => job.name === context.job);
-    let time =
-      new Date().getTime() - new Date(currentJob?.started_at ?? '').getTime();
-    const h = Math.floor(time / (1000 * 60 * 60));
-    time -= h * 1000 * 60 * 60;
-    const m = Math.floor(time / (1000 * 60));
-    time -= m * 1000 * 60;
-    const s = Math.floor(time / 1000);
-
-    let value = '';
-    if (h > 0) {
-      value += `${h} hour `;
-    }
-    if (m > 0) {
-      value += `${m} min `;
-    }
-    if (s > 0) {
-      value += `${s} sec`;
-    }
-
-    return {
-      value,
-      title: 'took',
-      short: true,
-    };
-  }
-
-  private async job(): Promise<Field | undefined> {
-    if (!this.includesField('job')) return undefined;
-
-    const { owner, repo } = context.repo;
-    const resp = await this.github?.actions.listJobsForWorkflowRun({
-      owner,
-      repo,
-      run_id: context.runId,
-    });
-    const jobId = resp?.data.jobs.find(job => job.name === context.job)?.id;
-
-    return {
-      title: 'job',
-      value: `<https://github.com/${owner}/${repo}/runs/${jobId}|${context.job}>`,
-      short: true,
-    };
-  }
-
-  private get commit(): Field | undefined {
-    if (!this.includesField('commit')) return undefined;
-
-    const { sha } = context;
-    const { owner, repo } = context.repo;
-
-    return {
-      title: 'commit',
-      value: `<https://github.com/${owner}/${repo}/commit/${sha}|${sha.slice(
-        0,
-        8,
-      )}>`,
-      short: true,
-    };
-  }
-
-  private get repo(): Field | undefined {
-    if (!this.includesField('repo')) return undefined;
-
-    const { owner, repo } = context.repo;
-
-    return {
-      title: 'repo',
-      value: `<https://github.com/${owner}/${repo}|${owner}/${repo}>`,
-      short: true,
-    };
-  }
-
-  private get eventName(): Field | undefined {
-    if (!this.includesField('eventName')) return undefined;
-
-    return {
-      title: 'eventName',
-      value: context.eventName,
-      short: true,
-    };
-  }
-
-  private get ref(): Field | undefined {
-    if (!this.includesField('ref')) return undefined;
-
-    return { title: 'ref', value: context.ref, short: true };
-  }
-
-  private get workflow(): Field | undefined {
-    if (!this.includesField('workflow')) return undefined;
-
-    const sha = context.payload.pull_request?.head.sha ?? context.sha;
-    const { owner, repo } = context.repo;
-
-    return {
-      title: 'workflow',
-      value: `<https://github.com/${owner}/${repo}/commit/${sha}/checks|${context.workflow}>`,
-      short: true,
-    };
-  }
-
   private getIdString(id: string): string {
     if (id.includes(subteamMention)) return `<!${id}>`;
     else return `<@${id}>`;
diff --git a/src/fields.ts b/src/fields.ts
new file mode 100644
index 0000000..fc40e78
--- /dev/null
+++ b/src/fields.ts
@@ -0,0 +1,189 @@
+import { context } from '@actions/github';
+import { GitHub } from './client';
+
+export interface Field {
+  title: string;
+  value: string;
+  short: boolean;
+}
+
+export class FieldFactory {
+  private github?: GitHub;
+  private fields: string[];
+
+  constructor(fields: string, github?: GitHub) {
+    this.fields = fields.replace(/ /g, '').split(',');
+    this.github = github;
+  }
+
+  includes(field: string) {
+    return this.fields.includes(field) || this.fields.includes('all');
+  }
+
+  filterField<T extends Array<Field | undefined>, U extends undefined>(
+    array: T,
+    diff: U,
+  ) {
+    return array.filter(item => item !== diff) as Exclude<
+      T extends { [K in keyof T]: infer U } ? U : never,
+      U
+    >[];
+  }
+
+  async attachments(): Promise<Field[]> {
+    return this.filterField(
+      [
+        this.includes('repo')
+          ? createAttachment('repo', await this.repo())
+          : undefined,
+        this.includes('message')
+          ? createAttachment('message', await this.message())
+          : undefined,
+        this.includes('commit')
+          ? createAttachment('commit', await this.commit())
+          : undefined,
+        this.includes('author')
+          ? createAttachment('author', await this.author())
+          : undefined,
+        this.includes('job')
+          ? createAttachment('job', await this.job())
+          : undefined,
+        this.includes('took')
+          ? createAttachment('took', await this.took())
+          : undefined,
+        this.includes('eventName')
+          ? createAttachment('eventName', await this.eventName())
+          : undefined,
+        this.includes('ref')
+          ? createAttachment('ref', await this.ref())
+          : undefined,
+        this.includes('workflow')
+          ? createAttachment('workflow', await this.workflow())
+          : undefined,
+      ],
+      undefined,
+    );
+  }
+
+  private async message(): Promise<string | undefined> {
+    const resp = await this.getCommit();
+    if (resp === undefined) return undefined;
+
+    const value = `<${resp.data.html_url}|${
+      resp.data.commit.message.split('\n')[0]
+    }>`;
+    process.env.AS_MESSAGE = value;
+    return value;
+  }
+
+  private async author(): Promise<string | undefined> {
+    const resp = await this.getCommit();
+    const author = resp?.data.commit.author;
+    if (author === undefined) return undefined;
+
+    const value = `${author.name}<${author.email}>`;
+    process.env.AS_AUTHOR = value;
+    return value;
+  }
+
+  private async took(): Promise<string | undefined> {
+    const resp = await this.github?.actions.listJobsForWorkflowRun({
+      owner: context.repo.owner,
+      repo: context.repo.repo,
+      run_id: context.runId,
+    });
+    const currentJob = resp?.data.jobs.find(job => job.name === context.job);
+    let time =
+      new Date().getTime() - new Date(currentJob?.started_at ?? '').getTime();
+    const h = Math.floor(time / (1000 * 60 * 60));
+    time -= h * 1000 * 60 * 60;
+    const m = Math.floor(time / (1000 * 60));
+    time -= m * 1000 * 60;
+    const s = Math.floor(time / 1000);
+
+    let value = '';
+    if (h > 0) {
+      value += `${h} hour `;
+    }
+    if (m > 0) {
+      value += `${m} min `;
+    }
+    if (s > 0) {
+      value += `${s} sec`;
+    }
+
+    process.env.AS_TOOK = value;
+    return value;
+  }
+
+  private async job(): Promise<string | undefined> {
+    const { owner } = context.repo;
+    const resp = await this.github?.actions.listJobsForWorkflowRun({
+      owner,
+      repo: context.repo.repo,
+      run_id: context.runId,
+    });
+    const jobId = resp?.data.jobs.find(job => job.name === context.job)?.id;
+
+    const value = `<https://github.com/${owner}/${context.repo.repo}/runs/${jobId}|${context.job}>`;
+    process.env.AS_JOB = value;
+    return value;
+  }
+
+  private async commit(): Promise<string> {
+    const { sha } = context;
+    const { owner, repo } = context.repo;
+
+    const value = `<https://github.com/${owner}/${repo}/commit/${sha}|${sha.slice(
+      0,
+      8,
+    )}>`;
+    process.env.AS_COMMIT = value;
+    return value;
+  }
+
+  private async repo(): Promise<string> {
+    const { owner, repo } = context.repo;
+
+    const value = `<https://github.com/${owner}/${repo}|${owner}/${repo}>`;
+    process.env.AS_REPO = value;
+    return value;
+  }
+
+  private async eventName(): Promise<string> {
+    const value = context.eventName;
+    process.env.AS_EVENT_NAME = value;
+    return value;
+  }
+
+  private async ref(): Promise<string> {
+    const value = context.ref;
+    process.env.AS_REF = value;
+    return value;
+  }
+
+  private async workflow(): Promise<string> {
+    const sha = context.payload.pull_request?.head.sha ?? context.sha;
+    const { owner, repo } = context.repo;
+
+    const value = `<https://github.com/${owner}/${repo}/commit/${sha}/checks|${context.workflow}>`;
+    process.env.AS_WORKFLOW = value;
+    return value;
+  }
+
+  private async getCommit() {
+    const { owner, repo } = context.repo;
+    const { sha: ref } = context;
+    return await this.github?.repos.getCommit({ owner, repo, ref });
+  }
+}
+
+function createAttachment(
+  title: string,
+  value?: string,
+  short?: boolean,
+): Field | undefined {
+  if (value === undefined) return undefined;
+  if (short === undefined) short = true;
+  return { title, value, short };
+}
diff --git a/src/main.ts b/src/main.ts
index 64cbb02..44745f9 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,5 @@
 import * as core from '@actions/core';
 import { Client, Success, Failure, Cancelled, Custom } from './client';
-import { IncomingWebhookSendArguments } from '@slack/webhook';
 
 async function run(): Promise<void> {
   try {
@@ -57,12 +56,7 @@ async function run(): Promise<void> {
         await client.send(await client.cancel(text));
         break;
       case Custom:
-        /* eslint-disable no-var */
-        var evalPayload: IncomingWebhookSendArguments = eval(
-          `evalPayload = ${custom_payload}`,
-        );
-        /* eslint-enable */
-        await client.send(evalPayload);
+        await client.send(await client.custom(custom_payload));
         break;
       default:
         throw new Error(


Test patch:
diff --git a/__tests__/client.test.ts b/__tests__/client.test.ts
index 2cc86ac..06ff2f8 100644
--- a/__tests__/client.test.ts
+++ b/__tests__/client.test.ts
@@ -18,7 +18,9 @@ import {
   Cancelled,
   Always,
   Field,
+  GitHub,
 } from '../src/client';
+import { FieldFactory } from '../src/fields';
 
 const repo = (): Field => {
   return {
@@ -89,31 +91,32 @@ const took = (): Field => {
   };
 };
 
-const fixedFields = (client: Client, sha?: string) => {
-  return client.filterField(
+const fixedFields = (fields: string, sha?: string) => {
+  const ff = new FieldFactory(fields);
+  return ff.filterField(
     [
-      client.includesField('repo') ? repo() : undefined,
-      client.includesField('message') ? message() : undefined,
-      client.includesField('commit') ? commit() : undefined,
-      client.includesField('author') ? author() : undefined,
-      client.includesField('job') ? job() : undefined,
-      client.includesField('took') ? took() : undefined,
-      client.includesField('eventName') ? eventName() : undefined,
-      client.includesField('ref') ? ref() : undefined,
-      client.includesField('workflow') ? workflow(sha) : undefined,
+      ff.includes('repo') ? repo() : undefined,
+      ff.includes('message') ? message() : undefined,
+      ff.includes('commit') ? commit() : undefined,
+      ff.includes('author') ? author() : undefined,
+      ff.includes('job') ? job() : undefined,
+      ff.includes('took') ? took() : undefined,
+      ff.includes('eventName') ? eventName() : undefined,
+      ff.includes('ref') ? ref() : undefined,
+      ff.includes('workflow') ? workflow(sha) : undefined,
     ],
     undefined,
   );
 };
 
-const getTemplate: any = (client: Client, text: string, sha?: string) => {
+const getTemplate: any = (fields: string, text: string, sha?: string) => {
   return {
     text,
     attachments: [
       {
         author_name: '',
         color: '',
-        fields: fixedFields(client, sha),
+        fields: fixedFields(fields, sha),
       },
     ],
     username: '',
@@ -179,7 +182,7 @@ describe('8398a7/action-slack', () => {
         fields: 'repo,message,commit,author,job,eventName,ref,workflow,took',
       };
       const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
-      const payload = getTemplate(client, `${successMsg}\n`);
+      const payload = getTemplate(withParams.fields, `${successMsg}\n`);
       payload.attachments[0].color = 'good';
       expect(await client.success('')).toStrictEqual(payload);
     });
@@ -199,7 +202,7 @@ describe('8398a7/action-slack', () => {
         fields: '',
       };
       const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
-      const payload = getTemplate(client, `${successMsg}\n`);
+      const payload = getTemplate(withParams.fields, `${successMsg}\n`);
       payload.attachments[0].color = 'good';
       expect(await client.success('')).toStrictEqual(payload);
     });
@@ -216,7 +219,7 @@ describe('8398a7/action-slack', () => {
         fields: '',
       };
       const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
-      const payload = getTemplate(client, `${failMsg}\n`);
+      const payload = getTemplate(withParams.fields, `${failMsg}\n`);
       payload.attachments[0].color = 'danger';
       expect(await client.fail('')).toStrictEqual(payload);
     });
@@ -233,7 +236,7 @@ describe('8398a7/action-slack', () => {
         fields: '',
       };
       const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
-      const payload = getTemplate(client, `${cancelMsg}\n`);
+      const payload = getTemplate(withParams.fields, `${cancelMsg}\n`);
       payload.attachments[0].color = 'warning';
       expect(await client.cancel('')).toStrictEqual(payload);
     });
@@ -253,7 +256,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, msg);
+    const payload = getTemplate(withParams.fields, msg);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -272,13 +275,13 @@ describe('8398a7/action-slack', () => {
     };
     let client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    let payload = getTemplate(client, msg);
+    let payload = getTemplate(withParams.fields, msg);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
 
     withParams.mention = '';
     client = new Client(withParams, process.env.GITHUB_TOKEN, '');
-    payload = getTemplate(client, msg);
+    payload = getTemplate(withParams.fields, msg);
     payload.attachments[0].color = 'danger';
     expect(await client.fail(msg)).toStrictEqual(payload);
   });
@@ -297,7 +300,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<!here> ${msg}`);
+    const payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -316,7 +319,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<!here> ${msg}`);
+    const payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -335,7 +338,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<!here> ${msg}`);
+    const payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'danger';
     expect(await client.fail(msg)).toStrictEqual(payload);
   });
@@ -354,7 +357,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<!here> ${msg}`);
+    const payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'warning';
     expect(await client.cancel(msg)).toStrictEqual(payload);
   });
@@ -373,15 +376,15 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    let payload = getTemplate(client, `<!here> ${msg}`);
+    let payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
 
-    payload = getTemplate(client, `<!here> ${msg}`);
+    payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'danger';
     expect(await client.fail(msg)).toStrictEqual(payload);
 
-    payload = getTemplate(client, `<!here> ${msg}`);
+    payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'warning';
     expect(await client.cancel(msg)).toStrictEqual(payload);
   });
@@ -400,7 +403,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<@user_id> ${msg}`);
+    const payload = getTemplate(withParams.fields, `<@user_id> ${msg}`);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -419,7 +422,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<!here> ${msg}`);
+    const payload = getTemplate(withParams.fields, `<!here> ${msg}`);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -438,7 +441,7 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<!channel> ${msg}`);
+    const payload = getTemplate(withParams.fields, `<!channel> ${msg}`);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -457,7 +460,10 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<!subteam^user_group_id> ${msg}`);
+    const payload = getTemplate(
+      withParams.fields,
+      `<!subteam^user_group_id> ${msg}`,
+    );
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -477,7 +483,7 @@ describe('8398a7/action-slack', () => {
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
     const payload = getTemplate(
-      client,
+      withParams.fields,
       `<!subteam^user_group_id> <!subteam^user_group_id2> ${msg}`,
     );
     payload.attachments[0].color = 'good';
@@ -498,7 +504,10 @@ describe('8398a7/action-slack', () => {
     };
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
-    const payload = getTemplate(client, `<@user_id> <@user_id2> ${msg}`);
+    const payload = getTemplate(
+      withParams.fields,
+      `<@user_id> <@user_id2> ${msg}`,
+    );
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -518,7 +527,7 @@ describe('8398a7/action-slack', () => {
     const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'mention test';
     const payload = getTemplate(
-      client,
+      withParams.fields,
       `<@user_id> <!subteam^user_group_id> ${msg}`,
     );
     payload.attachments[0].color = 'good';
@@ -540,7 +549,10 @@ describe('8398a7/action-slack', () => {
     let client = new Client(withParams, process.env.GITHUB_TOKEN, '');
     const msg = 'hello';
 
-    let payload = getTemplate(client, `<@user_id> <@user_id2> ${msg}`);
+    let payload = getTemplate(
+      withParams.fields,
+      `<@user_id> <@user_id2> ${msg}`,
+    );
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
   });
@@ -561,17 +573,17 @@ describe('8398a7/action-slack', () => {
     const msg = 'hello';
 
     // for success
-    let payload = getTemplate(client, msg);
+    let payload = getTemplate(withParams.fields, msg);
     payload.attachments[0].color = 'good';
     expect(await client.success(msg)).toStrictEqual(payload);
 
     // for cancel
-    payload = getTemplate(client, msg);
+    payload = getTemplate(withParams.fields, msg);
     payload.attachments[0].color = 'warning';
     expect(await client.cancel(msg)).toStrictEqual(payload);
 
     // for fail
-    payload = getTemplate(client, msg);
+    payload = getTemplate(withParams.fields, msg);
     payload.attachments[0].color = 'danger';
     expect(await client.fail(msg)).toStrictEqual(payload);
   });
@@ -589,7 +601,7 @@ describe('8398a7/action-slack', () => {
       fields: 'message,author',
     };
     const client = new Client(withParams, undefined, '');
-    const payload = getTemplate(client, `${successMsg}\n`);
+    const payload = getTemplate(withParams.fields, `${successMsg}\n`);
     payload.attachments[0].color = 'good';
     payload.attachments[0].fields = payload.attachments[0].fields.filter(
       (field: any) => !['message', 'author'].includes(field.title),
@@ -629,7 +641,7 @@ describe('8398a7/action-slack', () => {
       };
       const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
       const msg = 'mention test';
-      const payload = getTemplate(client, `<@user_id> ${msg}`, sha);
+      const payload = getTemplate(withParams.fields, `<@user_id> ${msg}`, sha);
       payload.attachments[0].color = 'good';
       expect(await client.success(msg)).toStrictEqual(payload);
     });
@@ -682,4 +694,29 @@ describe('8398a7/action-slack', () => {
 
     expect(fn).toBeCalledTimes(1);
   });
+  describe('.custom', () => {
+    it('is full fields', async () => {
+      const withParams: With = {
+        status: 'custom',
+        mention: '',
+        author_name: '',
+        if_mention: '',
+        username: '',
+        icon_emoji: '',
+        icon_url: '',
+        channel: '',
+        fields: 'all',
+      };
+      const client = new Client(withParams, process.env.GITHUB_TOKEN, '');
+      expect(
+        await client.custom(`{
+          text: \`\${process.env.AS_WORKFLOW}
+\${process.env.AS_JOB} (\${process.env.AS_COMMIT}) of \${process.env.AS_REPO}@master by \${process.env.AS_AUTHOR} succeeded in \${process.env.AS_TOOK}\`
+          }`),
+      ).toStrictEqual({
+        text: `<https://github.com/8398a7/action-slack/commit/b24f03a32e093fe8d55e23cfd0bb314069633b2f/checks|PR Checks>
+<https://github.com/8398a7/action-slack/runs/762195612|notification> (<https://github.com/8398a7/action-slack/commit/b24f03a32e093fe8d55e23cfd0bb314069633b2f|b24f03a3>) of <https://github.com/8398a7/action-slack|8398a7/action-slack>@master by 839<8398a7@gmail.com> succeeded in 1 hour 1 min 1 sec`,
+      });
+    });
+  });
 });