How to Upload Multiple Files React Native

Khi lập trình ứng dụng mobile, có khá nhiều thứ liên quan đến hình ảnh. Hướng dẫn sau đây sẽ giúp bạn có thể upload nhiều hình ảnh trong app React Native, tương tự Facebook hay instagram…

Chuẩn bị

Trong hướng dẫn này sử dụng api code dot.net core. Bạn có thể sử dụng ngôn ngữ khác như nodejs, php, java… Bạn có thể tham khảo thêm tại đây.

Trong React Native sử dụng 3 thư viện

npm install react-native-image-crop-picker'
npm install react-native-actionsheet
npm install axios

Các bạn tham khảo thêm ở github của thư viện để biết cách cấu hình chi tiết.

Code backend API

[HttpPost]
[Route("")]
public async Task<IActionResult> Post([FromForm]List<IFormFile> files)
{
    try
    {
        var today = DateTime.Today;
        var uploadPath = @"C:\web\files\files"; // Path.Combine(_hostingEnvironment.WebRootPath, "Content", "Files");
        uploadPath += $"\{today.Year}\{today.Month}".PadLeft(2, '0');
        if (!Directory.Exists(uploadPath))
        {
            Directory.CreateDirectory(uploadPath);
        }
        
        var fileUrls = new List<string>();
        foreach (var file in files)
        {
            if (file.Length > 0)
            {
                var fileExtension = Path.GetExtension(file.FileName);
                string fileName = $"{FileHelper.GenerateUniqueFileName}{fileExtension}";
                await file.CopyToAsync(stream);
                var fileUrl = $"files/{today.Year}/{today.Month}".PadLeft(2, '0') + "/" + fileName;
                fileUrls.Add(fileUrl);
            }
        }

        var fileItemResponse = new ResultDataObject
        {
            Data = fileUrls,
            Code = (int)ErrorCodeResultEnum.Ok,
            Message = "Ok"
        };
        return Ok(fileItemResponse);
    }
    catch (Exception ex)
    {
        var fileItemResponse = new ResultDataObject
        {
            Message = ex.Message,
            Code = (int)ErrorCodeResultEnum.Failure
        };
        return Ok(fileItemResponse);
    }
}

Code React Native

Chọn ảnh từ thư viện sử dụng ImagePicker, sử dụng state để lưu lại ảnh đã chọn

onActionSelectPhotoDone = index => {
    switch (index) {
      case 0:
        ImagePicker.openCamera({}).then(image => {
          this.setState({
            localPhotos: [...this.state.localPhotos, image]
          });
        });
        break;
      case 1:
        ImagePicker.openPicker({
          multiple: true,
          maxFiles: 10,
          mediaType: 'photo'
        }).then(images => {
          images.forEach((image) => {
           this.setState({
            localPhotos: [...this.state.localPhotos, image]
          });
          });
        }).catch(error => {
          alert(JSON.stringify(error));
        });
        break;
      default:
        break;
    }
  };

Code hiển thị hình ảnh sau khi chọn

renderListPhotos = localPhotos => {
    const photos = localPhotos.map((photo, index) => (
      <TouchableOpacity key={index}
        onPress={() => {
           this.showActionSheet(index);
        }}
      >
        <Image style={styles.photo} source={{ uri: photo.path }} />
      </TouchableOpacity>
    ));

    return photos;
  };

  renderSelectPhotoControl = localPhotos => {
    return (
      <View style={styles.sectionContainer}>
        <Text style={styles.sectionTitle}>Select photos</Text>
        <ScrollView style={styles.photoList} horizontal={true}>
          {this.renderListPhotos(localPhotos)}
          <TouchableOpacity onPress={this.onPressAddPhotoBtn.bind(this)}>
            <View style={[styles.addButton, styles.photo]}>
              <Text style={styles.addButtonText}>+</Text>
            </View>
          </TouchableOpacity>
        </ScrollView>
      </View>
    );
  };

Sử dụng axios & FormData để Upload

  onDoUploadPress() {
    const { localPhotos } = this.state;
    if (localPhotos && localPhotos.length > 0) {
      let formData = new FormData();
      localPhotos.forEach((image) => {
        const file = {
          uri: image.path,
          name: image.filename || Math.floor(Math.random() * Math.floor(999999999)) + '.jpg',
          type: image.mime || 'image/jpeg'
        };
        formData.append('files', file);
      });

      axios
        .post('https://api.tradingproedu.com/api/v1/fileupload', formData)
        .then(response => {
          this.setState({ logs: JSON.stringify(response.data) });
        })
        .catch(error => {
          alert(JSON.stringify(error));
        });
    } else {
      alert('No photo selected');
    }
  }

Chúc các bạn thành công.

Full source code https://github.com/tuanitpro/react-native-upload-files

SonarQube: Code Quality and Security

Trong quá trình lập trình & phát triển phần mềm chúng ta thường hay gặp vấn đề về quản lý chất lượng code, code smell, dirty code hay technical debt, thậm chí tồn tại lỗ hổng bảo mật. Nhất là khi dự án có sự tham gia của nhiều member, với trình độ kinh nghiệm khác nhau. Hoặc khi dự án chưa có rules, coding conventions, coding styles rõ ràng. Đến một ngày nào đó cần maintainance dự án hay develop thêm feature mới, chúng ta mới giật mình nhìn lại đống code cũ, tại sao nó lại tệ hại như vậy.

SonarQube là một open source platform, được phát triển bởi SonarSource dành cho việc kiểm tra liên tục chất lượng code (code quality), review code một cách tự động để phát hiện ra các bugs, code smell, lỗ hổng bảo mật cho 25+ ngôn ngữ lập trình khác nhau. SonarQube hỗ trợ báo cáo duplicated code, coding standards, unit tests, code coverage, code complexity, comments, bugs, and security vulnerabilities.

Cài đặt SonarQube trên Windows

Hướng dẫn cài đặt và bắt đầu với SonarQube. Bạn có thể sử dụng Docker hoặc download file về chạy bình thường. Yêu cầu máy có Java JDK 11 trở lên. Cấu hình máy tối thiểu 2GB RAM. https://docs.sonarqube.org/latest/requirements/requirements/

Đầu tiên vào https://www.sonarqube.org/downloads/ chọn bản Community (miễn phí). Giải nén và tìm thư mục bin để chạy. Đối với Windows có thể cài thành service để tiện sử dụng.

Nếu gặp lỗi liên quan JAVA, các bạn chú ý cài lại Java JDK. Sau đó tìm conf/wrapper.conf sửa lại dòng sau:

wrapper.java.command=C:\Program Files\Java\jdk-12.0.2\bin\java

Truy cập: http://localhost:9000 username/password: admin/admin để kiểm tra. Nếu thành công thì chúng ta xong bước cài đặt. Tiếp theo tích hợp dự án vào SonarQube để phân tích.

Tích hợp dự án của bạn vào SonarQube

Sau khi đăng nhập thành công, click vào http://localhost:9000/projects/create để tạo dự án mới. Nhập key & tên dự án, sau đó chuyển sang màn hình nhập key, chọn loại dự án của bạn, và download file scanner của nó về, giải nén và thêm vào biến môi trường %PATH%. Ví dụ mình làm về Windows, dot net core và reactjs thì cần

sonar-scanner-4.0.0.1744-windows
sonar-scanner-msbuild-4.6.2.2108-net46
sonar-scanner-msbuild-4.6.2.2108-netcoreapp2.0

Sau đó mở project của bạn và chạy câu lệnh theo hướng dẫn để sonarqube phân tích.

SonarScanner.MSBuild.exe begin /k:"KEY_CUA_BAN" /d:sonar.host.url="http://localhost:9000" /d:sonar.login="API_KEY_CUA_BAN"

MsBuild.exe /t:Rebuild

SonarScanner.MSBuild.exe end /d:sonar.login="API_KEY_CUA_BAN"

Kết quả sau khi áp dụng

Chúc các bạn thành công.

Dãy số Fibonacci trong C#

Quy luật của dãy số Fibonacci: số tiếp theo bằng tổng của 2 số trước, 2 số đầu tiên của dãy số là 0, 1. Ví dụ: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …

Sử dụng yield trong C# để trả về dãy số Fibonacci

static void Main()
{
 foreach(var value in Fibonacci())
 {
 Console.Write(value + " ");
 if(value > 1000) 
 {
 break;
 }
 }
}

static IEnumerable<int>  Fibonacci()
{
 int current = 0;
 int next = 1;
 while(true)
 {
 yield return current;
 int oldCurrent = current;
 current = next;
 next = next + oldCurrent;
 } 
}

Kết quả:

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Add thêm website trên VPS nginx CentOS

Thông thường mỗi VPS mình chỉ chạy cho một website mà thôi, tuy nhiên trong trường hợp bạn có nhiều website và muốn add thêm chạy trên VPS thì sao, hãy tham khảo bài hướng dẫn này để thực hiện.

Giới thiệu Virtual Hosts

Virtual Hosts được sử dụng để giúp cho một VPS có thể chạy được nhiều website khác nhau.

Theo như nginx website, Virtual Hosts được gọi là Server Blocks trên nginx, tuy nhiên cho dễ dàng giống như Apache nên mình sẽ gọi là Virtual Hosts trong bài này. Các bước add thêm website trên VPS nginx chạy CentOS bằng Virtual Hosts như sau:

Chuẩn bị server

Đã cài đặt sẵn webserver Nginx trên CentOS

Tạo thư mục chứa website

Ví dụ mình sẽ tạo thư mục ở folder /home/ nhé. Thay tuanitpro.com bằng domain của bạn.

mkdir -p /home/www/tuanitpro.com/public_html

Gán quyền

Đảm bảo cho website hoạt động bình thường

chown -R nginx:nginx /home/www/tuanitpro.com/public_html

Cài đặt Virtual Hosts

Thêm file cấu hình .conf cho domain mới

nano /etc/nginx/conf.d/tuanitpro.com.conf

Sử dụng đoạn code sau:

#
# tunanitpro.com configuration
#
server {
    listen       80;
    server_name tuanitpro.com;
    root /home/www/tuanitpro.com/public_html;
    location / {
        
        index index.php  index.html index.htm;
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }

    error_page  404              /404.html;
    location = /404.html {
        root   /home/www/tuanitpro.com/public_html;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /home/www/tuanitpro.com/public_html;
    }

    # pass the PHP scripts to FastCGI server
    #
    location ~ \.php$ {
        root           /home/www/tuanitpro.com/public_html;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME   $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

Lưu lại và thoát.

Reload Nginx

service nginx reload

Entity framework update modified fields only

Coding

 public virtual int Update(T entity, params Expression>[] properties)
        {
            if (entity.Id < 1)
            {
                return Insert(entity).Id;
            }
            if (properties?.Any() == true)
            {
                _dbContext.Attach(entity);
                foreach (var prop in properties)
                {
                    _dbContext.Entry(entity).Property(prop).IsModified = true;
                }
            }
            else
            {
                _dbContext.Entry(entity).State = EntityState.Modified;
            }

            return _dbContext.SaveChanges();
        }

        public virtual int Update(T entity, object replaceBy)
        {
            if (entity.Id < 1)
            {
                return Insert(entity).Id;
            }
            if (replaceBy == null)
            {
                return _dbContext.SaveChanges();
            }

            var properties = replaceBy.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);            
            if (properties?.Any() == true)
            {
                foreach (var prop in properties)
                {
                    var valueOfProp = prop.GetValue(replaceBy, null);
                    var propOfEntity = entity.GetType().GetProperty(prop.Name);
                    propOfEntity.SetValue(entity, Convert.ChangeType(valueOfProp, propOfEntity.PropertyType), null);

                    _dbContext.Entry(entity).Property(propOfEntity.Name).IsModified = true;
                }
            }
            else
            {
                _dbContext.Entry(entity).State = EntityState.Modified;
            }

            return _dbContext.SaveChanges();
        }

Using

var contact = new Contact { Id = 1, FullName = "Le Thanh Tuan" };

var rs1 = contactService.Update(contact, x => x.FullName);

var rs2 = contactService.Update(contact, new
{
	Id = 1,
	Phone = "0976060432",
	UserName = "tuanitpro",                
	FullName = "Le Thanh Tuan"                             
});

Handle errors in ASP.NET Core

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
if (env.IsProduction() || env.IsStaging())
{
app.UseExceptionHandler(options =>
{
options.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = “text/plain”;
await context.Response.WriteAsync(“CONTACT: 097 6060 432”);

var exceptionHandlerPathFeature =
context.Features.Get();
var now = System.DateTime.Now;
var eventLog = new EventLog
{
DateCreated = now,
RequestPath = exceptionHandlerPathFeature?.Path,
IPAddress = context.Request.HttpContext.Connection.RemoteIpAddress.ToString(),
Message = exceptionHandlerPathFeature?.Error.Source
};
string eventLogString = JsonConvert.SerializeObject(eventLog);

eventLogString += exceptionHandlerPathFeature?.Error.StackTrace + “\n\r”;

File.AppendAllText(“log.txt”, eventLogString);

ColorLife.Core.Helpers.IEmailProvider emailProvider = new ColorLife.Core.Helpers.EmailProvider(“[Urgent] Bug “, “[email protected]”);
emailProvider.Send(eventLogString, null);
});
});
app.UseHsts();
}

Pagination trong ReactJs

Khi làm các ứng dụng web, chúng ta luôn có nhu cầu lấy dữ liệu từ các remote server, API… các dữ liệu mạng xã hội, tin tức, shopping, thanh toán thì rất nhiều records, do đó chúng ta cần giải pháp phân trang để giảm tải sự hiển thị quá nhiều data gây khó chịu cho người dùng.

Hướng dẫn đơn giản sau đây giúp bạn dễ dàng phân trang trong app ReactJs

Ví dụ ở đây sử dụng API dữ liệu các quốc gia, 248 quốc gia tất cả. Chúng ta sẽ bắt đầu với ứng dụng react đơn giản.

Tạo một app react js

[code lang="js"]
npx create-react-js pagination
[/code]

Cài đặt các thư viện cần thiết

[code lang="js"]
npm i bootstrap countries-api prop-types react-flags
[/code]

Sau khi cài đặt thành công chúng ta copy folder flags ở địa chỉ node_modules\react-flags\vendor đưa vào folder public\img

Open file index.js, thêm code bootstrap

[code lang=”js”] import “bootstrap/dist/css/bootstrap.min.css”; [/code]

Components

Trong folder src, tạo folder components

Tạo 2 component

  • CountryCard.jsx Component
  • Pagination.jsx Component

CountryCard Component

[code lang=”js”] import React, { Component, Fragment } from ‘react’; import PropTypes from ‘prop-types’; import Flag from ‘react-flags’; class CountryCard extends Component { render() { const { cca2: code2 = “”, region = null, name = {} } = this.props.country || {}; return (
{name.common} {region}
) } } CountryCard.propTypes = { country: PropTypes.shape({ cca2: PropTypes.string.string, region: PropTypes.string.isRequired, name: PropTypes.shape({ common: PropTypes.string.isRequired }).isRequired }).isRequired } export default CountryCard [/code]

Pagination Component

[code lang=”js”] import React, { Component, Fragment } from “react”; import PropTypes from “prop-types”; const LEFT_PAGE = “LEFT”; const RIGHT_PAGE = “RIGHT”; const range = (from, to, step = 1) => { let i = from; const range = []; while (i <= to) { range.push(i); i += step; } return range; }; class Pagination extends Component { constructor(props) { super(props); const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props; this.pageLimit = typeof pageLimit === "number" ? pageLimit : 30; this.totalRecords = typeof totalRecords === "number" ? totalRecords : 0; this.pageNeighbours = typeof pageNeighbours === "number" ? Math.max(0, Math.min(pageNeighbours, 2)) : 0; this.totalPages = Math.ceil(this.totalRecords / this.pageLimit); this.state = { currentPage: 1 }; } componentDidMount() { this.gotoPage(1); } gotoPage = page => { const { onPageChanged = f => f } = this.props; const currentPage = Math.max(0, Math.min(page, this.totalPages)); const paginationData = { currentPage, totalPages: this.totalPages, pageLimit: this.pageLimit, totalRecords: this.totalRecords }; this.setState({ currentPage }, () => onPageChanged(paginationData)); }; handleClick = (page, evt) => { evt.preventDefault(); this.gotoPage(page); }; handleMoveLeft = evt => { evt.preventDefault(); this.gotoPage(this.state.currentPage – this.pageNeighbours * 2 – 1); }; handleMoveRight = evt => { evt.preventDefault(); this.gotoPage(this.state.currentPage + this.pageNeighbours * 2 + 1); }; fetchPageNumbers = () => { const totalPages = this.totalPages; const currentPage = this.state.currentPage; const pageNeighbours = this.pageNeighbours; const totalNumbers = this.pageNeighbours * 2 + 3; const totalBlocks = totalNumbers + 2; if (totalPages > totalBlocks) { let pages = []; const leftBound = currentPage – pageNeighbours; const rightBound = currentPage + pageNeighbours; const beforeLastPage = totalPages – 1; const startPage = leftBound > 2 ? leftBound : 2; const endPage = rightBound < beforeLastPage ? rightBound : beforeLastPage; pages = range(startPage, endPage); const pagesCount = pages.length; const singleSpillOffset = totalNumbers - pagesCount - 1; const leftSpill = startPage > 2; const rightSpill = endPage < beforeLastPage; const leftSpillPage = LEFT_PAGE; const rightSpillPage = RIGHT_PAGE; if (leftSpill && !rightSpill) { const extraPages = range(startPage - singleSpillOffset, startPage - 1); pages = [leftSpillPage, ...extraPages, ...pages]; } else if (!leftSpill && rightSpill) { const extraPages = range(endPage + 1, endPage + singleSpillOffset); pages = [...pages, ...extraPages, rightSpillPage]; } else if (leftSpill && rightSpill) { pages = [leftSpillPage, ...pages, rightSpillPage]; } return [1, ...pages, totalPages]; } return range(1, totalPages); }; render() { if (!this.totalRecords) return null; if (this.totalPages === 1) return null; const { currentPage } = this.state; const pages = this.fetchPageNumbers(); return ( ); } } Pagination.propTypes = { totalRecords: PropTypes.number.isRequired, pageLimit: PropTypes.number, pageNeighbours: PropTypes.number, onPageChanged: PropTypes.func }; export default Pagination; [/code]

App.js

[code lang=”js”] import React, { Component } from ‘react’; import Countries from ‘countries-api/lib/data/Countries.json’; import ‘./App.css’; import Pagination from “./components/Pagination”; import CountryCard from ‘./components/CountryCard’; class App extends Component { state = { allCountries: [], currentCountries: [], currentPage: null, totalPages: null }; componentDidMount() { const allCountries = Countries; this.setState({ allCountries }); } onPageChanged = data => { const { allCountries } = this.state; const { currentPage, totalPages, pageLimit } = data; const offset = (currentPage – 1) * pageLimit; const currentCountries = allCountries.slice(offset, offset + pageLimit); this.setState({ currentPage, currentCountries, totalPages }); }; mapCountry(country, index) { return } render() { const { allCountries, currentCountries, currentPage, totalPages } = this.state; const totalCountries = allCountries.length; if (totalCountries === 0) return null; const headerClass = [ “text-dark py-2 pr-4 m-0”, currentPage ? “border-gray border-right” : “” ] .join(” “) .trim(); const displayCountries = currentCountries.map(this.mapCountry); return (

{totalCountries}{” “} Countries

{currentPage && ( Page {currentPage} /{” “} {totalPages} )}
{displayCountries}
); } } export default App; [/code]

Download mã nguồn ở đây

GitHub

Sử dụng cache trong ASP.NET MVC với Redis Cache, Memory Cache

Khi lập trình ứng dụng chúng ta hay gặp vấn đề làm sao cho tối ưu tài nguyên của hệ thống, tăng tốc độ trải nghiệm từ phía end user. Đặc biệt là các ứng dụng web.

Cache là gì ? 
Lý thuyết: Cache là tên gọi của bộ nhớ đệm – nơi lưu trữ các dữ liệu nằm chờ các ứng dụng hay phần cứng xử lý. Mục đích của nó  để tăng tốc độ xử lý (có sẵn xài liền không cần tốn thời gian đi lùng sục tìm kéo về).

Thực tế: Cache là các dữ liệu trong phiên làm việc trước của các ứng dụng, chương trình mà hệ điều hành lưu lại nhằm giúp việc tải data trong các phiên làm việc sau được nhanh hơn.

Tip này hướng dẫn các bạn cách implement cache vào ứng dụng, sử dụng thư viện đã được wrap lại từ Memery Cache & Redis Cache

Đầu tiên tải về thư viện tại Nuget: https://www.nuget.org/packages/colorlife.tuanitpro.com/

Hoặc cài đặt trực tiếp trong Visual Studio

Tạo class Customer để test

public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

Sử dụng Memory Cache

  private static void MemoryCache_Customer_Test()
        {
            ICacheProviderFactory cacheProvideractory = new CacheProviderFactory();
            IDataCacheProvider dataCacheProvider = cacheProvideractory.CreateFactory();
            Customer customer = new Customer
            {
                FirstName = "Tuan",
                LastName = "Le"
            };
            string cacheKey = "Memory_Cache_Customer";
            dataCacheProvider.Set(cacheKey, customer);
            Console.WriteLine("Before cache: " + customer.FirstName + " " + customer.LastName);
             
            Console.WriteLine("After Update: " + customer.FirstName + " " + customer.LastName);

            var customerInCache = dataCacheProvider.Get(cacheKey);

            Console.WriteLine("Get Customer In MemoryCache: " + customerInCache.FirstName + " " + customerInCache.LastName);
        }

Sử dụng Radis Cache
Đầu tiên tải Redis cho Windows & cài đặt tại: https://github.com/MicrosoftArchive/redis/releases

Code:

  private static void RedisCache_Customer_Test()
        {
            ICacheProviderFactory cacheProvideractory = new CacheProviderFactory(new CacheProviderOptions
            {
                SlidingExpiration = TimeSpan.FromSeconds(30),
                ConnectionString = "localhost:6379" // mặc định
            }, CacheType.Redis);
            IDataCacheProvider dataCacheProvider = cacheProvideractory.CreateFactory();
            Customer customer = new Customer
            {
                FirstName = "Tuan",
                LastName = "Le"
            };
            string cacheKey = "RedisCache_Customer";
            dataCacheProvider.Set(cacheKey, customer);
            Console.WriteLine("Before cache: " + customer.FirstName + " " + customer.LastName);
            Thread.Sleep(5000);
            customer.LastName = "Le_Updated";
            Console.WriteLine("After Update: " + customer.FirstName + " " + customer.LastName);

            var customerInCache = dataCacheProvider.Get(cacheKey);

            Console.WriteLine("Customer In RedisCache: " + customerInCache.FirstName + " " + customerInCache.LastName);
        }

Thư viện còn hỗ trợ một số chức năng nho nhỏ khác, sẽ nói trong bài viết khác. Chúc các bạn thành công 🙂

Tạo bot discord check giá coinmarketcap

Các thư viện sử dụng

https://discord.js.org

https://www.npmjs.com/package/node-fetch

Yêu cầu máy đã cài nodejs

Tạo app trên discord: https://discordapp.com/developers/docs/intro

Lấy token để sử dụng sau:

Sử dụng Visual Code để code.

Tạo file bot.js

Code:

npm install discord.js

const Discord = require('discord.js');
const client = new Discord.Client();

const commandPrefix = "!";

client.on('ready', () => {
  console.log(`Logged in as ${client.user.tag}!`);
});

client.on('message', (message)=> {
    if(!message.content.startsWith(commandPrefix)) return;
    const args = message.content.slice(commandPrefix.length).trim().split(/ +/g);
    const command = args.shift().toLowerCase();
       
    switch(command){
    	case "hello":
			hello(message);
    	break;
    	case "help":
			help(message);
    	break;
    	case "ping":
			sendText(message, "Pong");
    	break;
    	case "clear":
    		clear(message);
    	break;
    	case "cmc":
    		coinmarketcap(message);
    	break;
    	case "c":
    		chart(message, args);
    	break;
    	case "p":
    		price(message, args);
    	break;    	
    	default:
    		sendText(message, "Command not found.")
    	break;
    }
});

client.login('NDQyMDAyMTAyODAyMzE3MzMz.DdMfbw.3eV-Qk5N2TpDIDgatQb1ILeD0Hc');

Các hàm chính trong bot

function hello(message){
	message.channel.send("Hello " + message.author + "! Nice to meet you. :smiley: ");
}
function help(message){
	 let embed = new Discord.RichEmbed()            
            .setAuthor("Hello world", "http://icons.iconarchive.com/icons/froyoshark/enkel/256/Bitcoin-icon.png")
            .addField("!hello", "Sends a friendly message!")
            .addField("!help", "Sends this help embed")
            .addField("!cmc", "Coin Market Cap")
            .addField("!ping", "Ping")
            .addField("!p", "Price of coin. Ex: BTCUSDT or price BTC")
            .setTitle("Bot commands:")
            .setFooter("Here you have all bot commands you can use!")
            .setColor("AQUA");        
        message.channel.send({embed: embed});
}
function sendText(message, text){
	message.channel.send(text).then(msg=>{msg.delete(10000)}); 
    message.delete(12000);
}

function clear(message){
	if (message.member.hasPermission("MANAGE_MESSAGES")) {
        message.channel.fetchMessages()
           .then(function(list){
                message.channel.bulkDelete(list);
            }, function(err){message.channel.send("ERROR: ERROR CLEARING CHANNEL.")})                        
    }
    else{
    	console.log("You don't have permission");
    }

Hàm lấy giá trên coinmarketcap.com

npm install node-fetch

function coinmarketcap(message){
	    let url = 'https://api.coinmarketcap.com/v2/global/';	
		let bitcoin_percentage_of_market_cap='';
		let total_market_cap= '';
		let total_volume_24h = '';
		let last_updated = '';
		fetch(url)
    	.then(res => res.json())
    	.then(json => {
    		// console.log(json);
    		bitcoin_percentage_of_market_cap =  json.data.bitcoin_percentage_of_market_cap;
    		total_market_cap = json.data.quotes.USD.total_market_cap;
 			total_volume_24h = json.data.quotes.USD.total_volume_24h;
 			last_updated = json.data.last_updated;
			
 			let embed = new Discord.RichEmbed()
 			.setAuthor("Coinmarketcap", "http://icons.iconarchive.com/icons/froyoshark/enkel/256/Bitcoin-icon.png")
            .addField("Market Cap (USD):", numberFormat(total_market_cap))
            .addField("24h Vol (USD): ", numberFormat(total_volume_24h))
            .addField("BTC Dominance(%): ", bitcoin_percentage_of_market_cap)            
            .setFooter("Last Updated: " + timeConverter(last_updated))
            .setColor("AQUA");

        // Send the embed with message.channel.send()
        message.channel.send({embed: embed}).then(msg=>{msg.delete(10000)}); 
        message.delete(12000);       
    	});    	 
}

Thêm bot vào Server Discord

Open link & thay bằng client_id của bạn trên trình duyệt

Khởi chạy bot

node bot.js

Demo

 

Source code:  GitHub

Chúc các bạn thành công

DONATE

Bitcoin address: 1tmxPQbmycQMehAAZ8FshbvEoQZ6Ri7PT

ETH Address: 0xfe17aaf16bceb4311795b2e8ff0199640bdce54a

ETC Address: 0xfe17aaf16bceb4311795b2e8ff0199640bdce54a

XVG Address: D9iP7fhKbHJKViwUG9MWcAt3JPdCuxnCZJ