Skip to content

Instantly share code, notes, and snippets.

@codehz
Created December 24, 2017 14:38
Show Gist options
  • Save codehz/9c2d3a8b52a0b101256b40b525b2dbea to your computer and use it in GitHub Desktop.
Save codehz/9c2d3a8b52a0b101256b40b525b2dbea to your computer and use it in GitHub Desktop.
Steam-Discount-Sticker
package main
import (
"bytes"
"fmt"
"github.com/chai2010/webp"
"github.com/fogleman/gg"
"github.com/satori/go.uuid"
"golang.org/x/image/font"
"golang.org/x/image/tiff"
"gopkg.in/telegram-bot-api.v4"
"image"
"image/color"
"image/draw"
"image/gif"
"image/png"
"log"
"math"
"net/http"
"os"
"strconv"
"strings"
)
var discountBackground = color.RGBA{0x8b, 0xb0, 0x06, 0xff}
var antiDiscountBackground = color.RGBA{0xB0, 0x19, 0x05, 0xff}
var discountColor = color.RGBA{0x00, 0x00, 0x00, 0xff}
var priceBackground = color.RGBA{0x14, 0x1f, 0x2c, 0xcc}
var fromColor = color.RGBA{0x57, 0x71, 0x80, 0xff}
var toColor = color.RGBA{0xac, 0xdb, 0xf5, 0xff}
var palette color.Palette = color.Palette{
discountBackground,
discountColor,
priceBackground,
fromColor,
toColor,
color.RGBA{0xff, 0, 0, 0xff},
color.RGBA{0xFF, 0xFF, 0xFF, 0xFF},
}
const compactSpacing = 4
const discountSize = 64
const priceSize = 32
const leftPaddingHori = 20
const leftPaddingVert = 8
const rightPaddingHori = 12
const rightPaddingVert = 2
var fontFile = os.Args[1]
var botToken = os.Args[2]
var chatID, _ = strconv.ParseInt(os.Args[3], 10, 64)
var ft_large = loadFont(26 * 2)
var ft_normal = loadFont(13 * 2)
var ft_small = loadFont(12 * 2)
var dc_test = gg.NewContext(1, 1)
func loadFont(size int32) font.Face {
ret, err := gg.LoadFontFace(fontFile, float64(size))
if err != nil {
log.Fatal(err)
}
return ret
}
func fetch(data map[string][]string, key string, def string) string {
val, ok := data[key]
if ok {
return val[0]
} else {
return def
}
}
func measureLarge(str string) (float64, float64) {
dc_test.SetFontFace(ft_large)
return dc_test.MeasureString(str)
}
func measureNormal(str string) (float64, float64) {
dc_test.SetFontFace(ft_normal)
return dc_test.MeasureString(str)
}
func measureSmall(str string) (float64, float64) {
dc_test.SetFontFace(ft_small)
return dc_test.MeasureString(str)
}
func drawBlock(str string) image.Image {
w, h := measureNormal(str)
dc := gg.NewContext(int(w+16), int(h+8))
dc.SetRGB(1, 0, 0)
dc.Clear()
dc.SetFontFace(ft_normal)
dc.SetRGB(1, 1, 1)
dc.DrawStringAnchored(str, w/2+8, h/2+4, 0.5, 0.35)
return dc.Image()
}
func getPngBuffer(m image.Image) []byte {
buffer := new(bytes.Buffer)
if err := png.Encode(buffer, m); err != nil {
log.Printf("unable to encode png: %s\n", err)
}
return buffer.Bytes()
}
func outputImage(w http.ResponseWriter, m image.Image, f string) {
buffer := new(bytes.Buffer)
t := "image/png"
switch f {
case "gif":
nm := image.NewPaletted(m.Bounds(), palette)
draw.FloydSteinberg.Draw(nm, m.Bounds(), m, image.ZP)
gif.Encode(buffer, nm, &gif.Options{})
t = "image/gif"
break
case "tiff":
tiff.Encode(buffer, m, nil)
t = "image/tiff"
case "png":
if err := png.Encode(buffer, m); err != nil {
log.Printf("unable to encode image: %s\n", err)
}
case "webp":
fallthrough
default:
if err := webp.Encode(buffer, m, &webp.Options{Lossless: true}); err != nil {
log.Printf("unable to encode webp: %s\n", err)
}
}
w.Header().Set("Content-Type", t)
w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))
if _, err := w.Write(buffer.Bytes()); err != nil {
log.Printf("unable to send image: %s\n", err)
}
}
func drawTarget(dis string, sFrom string, sTo string) image.Image {
leftWidth, height1 := measureLarge(dis)
rightWidth1, height2p1 := measureSmall(sFrom)
rightWidth2, height2p2 := measureNormal(sTo)
finalLeftWidth := leftWidth + 2*leftPaddingHori
finalRightWidth := math.Max(rightWidth1, rightWidth2) + 2*rightPaddingHori
finalWidth := finalLeftWidth + finalRightWidth
finalHeight := math.Max(height1+leftPaddingVert*2, height2p1+height2p2+4*rightPaddingVert)
dc := gg.NewContext(int(finalWidth), int(finalHeight))
dc.SetColor(priceBackground)
dc.Clear()
if strings.HasPrefix(dis, "-") {
dc.SetColor(discountBackground)
} else {
dc.SetColor(antiDiscountBackground)
}
dc.DrawRectangle(0, 0, finalLeftWidth, finalHeight)
dc.Fill()
dc.SetColor(discountColor)
dc.SetFontFace(ft_large)
dc.DrawStringAnchored(dis, leftPaddingHori, finalHeight/2, 0, 0.35)
dc.SetColor(fromColor)
dc.SetFontFace(ft_small)
dc.DrawStringAnchored(sFrom, finalWidth-rightPaddingHori, finalHeight/4+rightPaddingVert, 1, 0.35)
dc.DrawLine(finalWidth-rightPaddingHori-rightWidth1, finalHeight/4, finalWidth-rightPaddingHori, finalHeight/4)
dc.Stroke()
dc.SetColor(toColor)
dc.SetFontFace(ft_normal)
dc.DrawStringAnchored(sTo, finalWidth-rightPaddingHori, finalHeight*3/4-rightPaddingVert, 1, 0.35)
return dc.Image()
}
func drawCompactTarget(dis string, sFrom string, sTo string) image.Image {
width1, height1 := measureSmall(dis)
width2, height2 := measureSmall(sFrom)
width3, height3 := measureSmall(sTo)
finalWidth := compactSpacing*5 + width1 + width2 + width3
finalHeight := rightPaddingVert*2 + math.Max(math.Max(height1, height2), height3)
dc := gg.NewContext(int(finalWidth), int(finalHeight))
dc.SetColor(priceBackground)
dc.Clear()
if strings.HasPrefix(dis, "-") {
dc.SetColor(discountBackground)
} else {
dc.SetColor(antiDiscountBackground)
}
dc.DrawRectangle(0, 0, compactSpacing*2+width1, finalHeight)
dc.Fill()
dc.SetColor(discountColor)
dc.SetFontFace(ft_small)
dc.DrawStringAnchored(dis, compactSpacing+width1/2, finalHeight/2, 0.5, 0.35)
dc.SetColor(fromColor)
dc.DrawStringAnchored(sFrom, compactSpacing*3+width1+width2/2, finalHeight/2, 0.5, 0.35)
dc.DrawLine(compactSpacing*3+width1, finalHeight/2, compactSpacing*3+width1+width2, finalHeight/2)
dc.Stroke()
dc.SetColor(toColor)
dc.DrawStringAnchored(sTo, finalWidth-compactSpacing-width3/2, finalHeight/2, 0.5, 0.35)
return dc.Image()
}
func drawPriceTarget(sPrice string) image.Image {
width, height := measureSmall(sPrice)
finalWidth := width + 2*rightPaddingHori
finalHeight := height + 2*rightPaddingVert
dc := gg.NewContext(int(finalWidth), int(finalHeight))
dc.SetColor(priceBackground)
dc.Clear()
dc.SetColor(toColor)
dc.SetFontFace(ft_small)
dc.DrawStringAnchored(sPrice, finalWidth/2, finalHeight/2, 0.5, 0.35)
return dc.Image()
}
func drawDiscountTraget(sDiscount string) image.Image {
width, height := measureNormal(sDiscount)
finalWidth := width + 2*compactSpacing
finalHeight := height + 2*rightPaddingVert
dc := gg.NewContext(int(finalWidth), int(finalHeight))
dc.SetColor(discountBackground)
dc.Clear()
dc.SetColor(discountColor)
dc.SetFontFace(ft_normal)
dc.DrawStringAnchored(sDiscount, finalWidth/2, finalHeight/2, 0.5, 0.35)
return dc.Image()
}
func fetchPriceFormat(vals map[string][]string) (func(float64) string, error) {
vSym := fetch(vals, "currncy", "USD")
return Money(vSym)
}
func mixArr(src []string, dst []string) []string {
if len(src) >= len(dst) {
return src
}
copy(dst, src)
return dst
}
func gen(w http.ResponseWriter, r *http.Request) {
vals := r.URL.Query()
vFormat := fetch(vals, "format", "png")
vFrom, err := strconv.ParseFloat(fetch(vals, "from", "100"), 64)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@from:%s", err)), vFormat)
return
}
vTo, err := strconv.ParseFloat(fetch(vals, "to", "0"), 64)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@to:%s", err)), vFormat)
return
}
ac, err := fetchPriceFormat(vals)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@to:%s", err)), vFormat)
return
}
sFrom := ac(vFrom)
sTo := ac(vTo)
sDiscount := fmt.Sprintf("%.0f%%", (vTo-vFrom)*100/vFrom)
outputImage(w, drawTarget(sDiscount, sFrom, sTo), vFormat)
}
func proc(args []string, fn func(string, string, string) image.Image) (image.Image, error) {
params := mixArr(args, []string{"100", "0", "USD"})
vFrom, err := strconv.ParseFloat(params[0], 64)
if err != nil {
return nil, err
}
vTo, err := strconv.ParseFloat(params[1], 64)
if err != nil {
return nil, err
}
ac, err := Money(params[2])
if err != nil {
return nil, err
}
sFrom := ac(vFrom)
sTo := ac(vTo)
sDiscount := fmt.Sprintf("%.0f%%", (vTo-vFrom)*100/vFrom)
return fn(sDiscount, sFrom, sTo), nil
}
func genCompact(w http.ResponseWriter, r *http.Request) {
vals := r.URL.Query()
vFormat := fetch(vals, "format", "png")
vFrom, err := strconv.ParseFloat(fetch(vals, "from", "100"), 64)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@from:%s", err)), vFormat)
return
}
vTo, err := strconv.ParseFloat(fetch(vals, "to", "0"), 64)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@to:%s", err)), vFormat)
return
}
ac, err := fetchPriceFormat(vals)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@to:%s", err)), vFormat)
return
}
sFrom := ac(vFrom)
sTo := ac(vTo)
sDiscount := fmt.Sprintf("%.0f%%", (vTo-vFrom)*100/vFrom)
outputImage(w, drawCompactTarget(sDiscount, sFrom, sTo), vFormat)
}
func genPrice(w http.ResponseWriter, r *http.Request) {
vals := r.URL.Query()
vFormat := fetch(vals, "format", "png")
vPrice, err := strconv.ParseFloat(fetch(vals, "price", "100"), 64)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@price:%s", err)), vFormat)
return
}
ac, err := fetchPriceFormat(vals)
if err != nil {
outputImage(w, drawBlock(fmt.Sprintf("Error@price:%s", err)), vFormat)
return
}
outputImage(w, drawPriceTarget(ac(vPrice)), vFormat)
}
func procPrice(args []string) (image.Image, error) {
params := mixArr(args, []string{"100", "USD"})
vPrice, err := strconv.ParseFloat(params[0], 64)
if err != nil {
return nil, err
}
ac, err := Money(params[1])
if err != nil {
return nil, err
}
sPrice := ac(vPrice)
return drawPriceTarget(sPrice), nil
}
func genDiscount(w http.ResponseWriter, r *http.Request) {
vals := r.URL.Query()
vFormat := fetch(vals, "format", "png")
sDiscount := fetch(vals, "discount", "100%")
outputImage(w, drawDiscountTraget(sDiscount), vFormat)
}
func procDiscount(args []string) (image.Image, error) {
params := mixArr(args, []string{"-100%"})
sDiscount := params[0]
return drawDiscountTraget(sDiscount), nil
}
func getSticker(bot *tgbotapi.BotAPI, args []string) (string, error) {
if len(args) == 0 {
return "", nil
}
temp := []string{}
if len(args) > 1 {
temp = args[1:]
}
var img image.Image
var perr error
switch args[0] {
case "full":
img, perr = proc(temp, drawTarget)
case "compact":
img, perr = proc(temp, drawCompactTarget)
case "price":
img, perr = procPrice(temp)
case "discount":
img, perr = procDiscount(temp)
default:
return "", nil
}
if perr != nil {
return "", perr
}
ret, err := bot.Send(tgbotapi.NewStickerUpload(chatID, tgbotapi.FileBytes{
Name: args[0],
Bytes: getPngBuffer(img),
}))
if err != nil {
log.Printf("send sticker error: %s\n", err)
return "", err
}
go func(id int) {
_, err = bot.DeleteMessage(tgbotapi.DeleteMessageConfig{
ChatID: chatID,
MessageID: ret.MessageID,
})
}(ret.MessageID)
return ret.Sticker.FileID, nil
}
type InlineQueryResultCachedSticker struct {
Type string `json:"type"`
ID string `json:"id"`
StickerFileId string `json:"sticker_file_id"`
}
func getInstantHelpMessage(sc string, count int) string {
var list []string
switch sc {
case "full":
fallthrough
case "compact":
list = []string{"original_price 100", "final_price 0", "currency USD", "!"}
case "discount":
list = []string{"discount_text -100%", "!"}
case "price":
list = []string{"final_price 0", "currency USD", "!"}
case "help":
fallthrough
case "":
return "full compact price discount (ends with !)"
default:
return "Unrecognized subcommands: " + sc
}
if count < len(list) {
return "next: " + list[count]
} else if count == len(list) {
return ""
} else {
return fmt.Sprintf("Overflow!!(%d/%d)", count, len(list))
}
}
func getHelpMessage(sc string) string {
switch sc {
case "full":
return "Sub command full:[original_price 100] [final_price 0] [currency USD] !"
case "compact":
return "Sub command compact:[original_price 100] [final_price 0] [currency USD] !"
case "discount":
return "Sub command discount:[discount_text -100%] !"
case "price":
return "Sub command price:[final_price 0] [currency USD] !"
case "help":
fallthrough
case "":
return "Commands: full compact price discount (inline mode must ends with !)"
default:
return "Unrecognized subcommands: " + sc
}
}
func filterSc(src string) string {
switch src {
case "full":
fallthrough
case "compact":
fallthrough
case "discount":
fallthrough
case "price":
return src
default:
return "help"
}
}
func main() {
bot, err := tgbotapi.NewBotAPI(botToken)
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/full", gen)
http.HandleFunc("/compact", genCompact)
http.HandleFunc("/price", genPrice)
http.HandleFunc("/discount", genDiscount)
//updates := bot.ListenForWebhook("/bot"+botToken)
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates, err := bot.GetUpdatesChan(u)
go http.ListenAndServe(":8888", nil)
for update := range updates {
log.Printf("%+v\n", update)
go func(update tgbotapi.Update) {
if query := update.InlineQuery; query != nil {
cmd := strings.Fields(query.Query)
sc := "help"
if len(cmd) > 0 {
sc = cmd[0]
}
var results []interface{} = []interface{}{}
tip := getInstantHelpMessage(sc, len(cmd)-1)
if tip == "" && len(cmd) > 0 && cmd[len(cmd)-1] == "!" {
fileId, err := getSticker(bot, cmd)
if err != nil {
tip = fmt.Sprintf("error:%s", err)
}
if fileId != "" {
results = []interface{}{InlineQueryResultCachedSticker{
Type: "sticker",
ID: uuid.NewV4().String(),
StickerFileId: fileId,
}}
}
}
var err error
if tip != "" {
_, err = bot.AnswerInlineQuery(tgbotapi.InlineConfig{
InlineQueryID: query.ID,
Results: results,
SwitchPMText: tip,
SwitchPMParameter: filterSc(sc),
})
} else {
_, err = bot.AnswerInlineQuery(tgbotapi.InlineConfig{
InlineQueryID: query.ID,
Results: results,
})
}
if err != nil {
log.Printf("answer inline error: %s", err)
}
} else if msg := update.Message; msg != nil {
log.Printf("%+v\n", msg.Chat)
if strings.HasPrefix(msg.Text, "/start") || strings.HasPrefix(msg.Text, "/help") {
fds := strings.Fields(msg.Text)
sc := ""
if len(fds) > 1 {
sc = fds[1]
}
var reply tgbotapi.MessageConfig = tgbotapi.NewMessage(msg.Chat.ID, getHelpMessage(sc))
_, err := bot.Send(reply)
if err != nil {
log.Printf("Send failed: %s\n", err)
}
}
}
}(update)
}
}
package main
import "fmt"
import "regexp"
import "bytes"
import "strings"
type currency struct {
SymbolFirst bool
SepMode bool
HasDecimalMark bool
Symbol string
Name string
}
var numberRegexp = regexp.MustCompile("\\d+")
func procDec(src string, ch rune) string {
var buffer bytes.Buffer
offset := len(src) % 3
for i := 0; i <= len(src)/3; i++ {
if i == 0 {
buffer.WriteString(src[0:offset])
if offset == 0 {
continue
}
} else {
buffer.WriteString(src[offset+i*3-3 : offset+i*3])
}
buffer.WriteRune(ch)
}
ret := buffer.String()
return ret[0 : len(ret)-1]
}
func Money(curr string) (func(float64) string, error) {
if mode, ok := currencies[curr]; ok {
return func(price float64) string {
fstr := "%.f"
if mode.HasDecimalMark {
fstr = "%.2f"
}
DecimalMark := "."
ThousandsSeparator := ','
if !mode.SepMode {
DecimalMark = ","
ThousandsSeparator = '.'
}
numParts := numberRegexp.FindAllString(fmt.Sprintf(fstr, price), 2)
numParts[0] = procDec(numParts[0], ThousandsSeparator)
whole := strings.Join(numParts, DecimalMark)
if mode.SymbolFirst {
return fmt.Sprintf("%s %s", mode.Symbol, whole)
} else {
return fmt.Sprintf("%s %s", whole, mode.Symbol)
}
}, nil
}
return nil, fmt.Errorf("Unrecognized Currency")
}
const T = true
const F = false
var currencies = map[string]currency{
"AED": currency{T, T, T, "د.إ", "United Arab Emirates Dirham"},
"AFN": currency{F, T, T, "؋", "Afghan Afghani"},
"ALL": currency{F, T, T, "L", "Albanian Lek"},
"AMD": currency{F, T, T, "դր.", "Armenian Dram"},
"ANG": currency{T, F, T, "ƒ", "Netherlands Antillean Gulden"},
"AOA": currency{F, T, T, "Kz", "Angolan Kwanza"},
"ARS": currency{T, F, T, "$", "Argentine Peso"},
"AUD": currency{T, T, T, "$", "Australian Dollar"},
"AWG": currency{F, T, T, "ƒ", "Aruban Florin"},
"AZN": currency{T, T, T, "₼", "Azerbaijani Manat"},
"BAM": currency{T, T, T, "КМ", "Bosnia and Herzegovina Convertible Mark"},
"BBD": currency{F, T, T, "$", "Barbadian Dollar"},
"BDT": currency{T, T, T, "৳", "Bangladeshi Taka"},
"BGN": currency{F, T, T, "лв", "Bulgarian Lev"},
"BHD": currency{T, T, T, "ب.د", "Bahraini Dinar"},
"BIF": currency{F, T, T, "Fr", "Burundian Franc"},
"BMD": currency{T, T, T, "$", "Bermudian Dollar"},
"BND": currency{T, T, T, "$", "Brunei Dollar"},
"BOB": currency{T, T, T, "Bs.", "Bolivian Boliviano"},
"BRL": currency{T, F, T, "R$", "Brazilian Real"},
"BSD": currency{T, T, T, "$", "Bahamian Dollar"},
"BTC": currency{T, T, T, "B⃦", "Bitcoin"},
"BTN": currency{F, T, T, "Nu.", "Bhutanese Ngultrum"},
"BWP": currency{T, T, T, "P", "Botswana Pula"},
"BYR": currency{F, T, T, "Br", "Belarusian Ruble"},
"BZD": currency{T, T, T, "$", "Belize Dollar"},
"CAD": currency{T, T, T, "$", "Canadian Dollar"},
"CDF": currency{F, T, T, "Fr", "Congolese Franc"},
"CHF": currency{T, T, T, "Fr", "Swiss Franc"},
"CLF": currency{T, F, T, "UF", "Unidad de Fomento"},
"CLP": currency{T, F, F, "$", "Chilean Peso"},
"CNY": currency{T, T, F, "¥", "Chinese Renminbi Yuan"},
"COP": currency{T, F, F, "$", "Colombian Peso"},
"CRC": currency{T, F, F, "₡", "Costa Rican Colón"},
"CUC": currency{F, T, T, "$", "Cuban Convertible Peso"},
"CUP": currency{T, T, T, "$", "Cuban Peso"},
"CVE": currency{F, T, T, "$", "Cape Verdean Escudo"},
"CZK": currency{F, F, T, "Kč", "Czech Koruna"},
"DJF": currency{F, T, T, "Fdj", "Djiboutian Franc"},
"DKK": currency{F, F, T, "kr", "Danish Krone"},
"DOP": currency{T, T, T, "$", "Dominican Peso"},
"DZD": currency{F, T, T, "د.ج", "Algerian Dinar"},
"EEK": currency{F, T, T, "KR", "Estonian Kroon"},
"EGP": currency{T, T, T, "ج.م", "Egyptian Pound"},
"ERN": currency{F, T, T, "Nfk", "Eritrean Nakfa"},
"ETB": currency{F, T, T, "Br", "Ethiopian Birr"},
"EUR": currency{T, F, T, "€", "Euro"},
"FJD": currency{F, T, T, "$", "Fijian Dollar"},
"FKP": currency{F, T, T, "£", "Falkland Pound"},
"GBP": currency{T, T, T, "£", "British Pound"},
"GEL": currency{F, T, T, "ლ", "Georgian Lari"},
"GHS": currency{T, T, T, "₵", "Ghanaian Cedi"},
"GIP": currency{T, T, T, "£", "Gibraltar Pound"},
"GMD": currency{F, T, T, "D", "Gambian Dalasi"},
"GNF": currency{F, T, T, "Fr", "Guinean Franc"},
"GTQ": currency{T, T, T, "Q", "Guatemalan Quetzal"},
"GYD": currency{F, T, T, "$", "Guyanese Dollar"},
"HKD": currency{T, T, T, "$", "Hong Kong Dollar"},
"HNL": currency{T, T, T, "L", "Honduran Lempira"},
"HRK": currency{T, F, T, "kn", "Croatian Kuna"},
"HTG": currency{F, T, T, "G", "Haitian Gourde"},
"HUF": currency{F, F, T, "Ft", "Hungarian Forint"},
"IDR": currency{T, F, F, "Rp", "Indonesian Rupiah"},
"ILS": currency{T, T, T, "₪", "Israeli New Sheqel"},
"INR": currency{T, T, T, "₹", "Indian Rupee"},
"IQD": currency{F, T, T, "ع.د", "Iraqi Dinar"},
"IRR": currency{T, T, T, "﷼", "Iranian Rial"},
"ISK": currency{T, F, T, "kr", "Icelandic Króna"},
"JEP": currency{T, T, T, "£", "Jersey Pound"},
"JMD": currency{T, T, T, "$", "Jamaican Dollar"},
"JOD": currency{T, T, T, "د.ا", "Jordanian Dinar"},
"JPY": currency{T, T, F, "¥", "Japanese Yen"},
"KES": currency{T, T, T, "KSh", "Kenyan Shilling"},
"KGS": currency{F, T, T, "som", "Kyrgyzstani Som"},
"KHR": currency{F, T, T, "៛", "Cambodian Riel"},
"KMF": currency{F, T, T, "Fr", "Comorian Franc"},
"KPW": currency{F, T, T, "₩", "North Korean Won"},
"KRW": currency{T, T, F, "₩", "South Korean Won"},
"KWD": currency{T, T, T, "د.ك", "Kuwaiti Dinar"},
"KYD": currency{T, T, T, "$", "Cayman Islands Dollar"},
"KZT": currency{F, T, F, "〒", "Kazakhstani Tenge"},
"LAK": currency{F, T, T, "₭", "Lao Kip"},
"LBP": currency{T, T, T, "ل.ل", "Lebanese Pound"},
"LKR": currency{F, T, T, "₨", "Sri Lankan Rupee"},
"LRD": currency{F, T, T, "$", "Liberian Dollar"},
"LSL": currency{F, T, T, "L", "Lesotho Loti"},
"LTL": currency{F, T, T, "Lt", "Lithuanian Litas"},
"LVL": currency{T, T, T, "Ls", "Latvian Lats"},
"LYD": currency{F, T, T, "ل.د", "Libyan Dinar"},
"MAD": currency{F, T, T, "د.م.", "Moroccan Dirham"},
"MDL": currency{F, T, T, "L", "Moldovan Leu"},
"MGA": currency{T, T, T, "Ar", "Malagasy Ariary"},
"MKD": currency{F, T, T, "ден", "Macedonian Denar"},
"MMK": currency{F, T, T, "K", "Myanmar Kyat"},
"MNT": currency{F, T, T, "₮", "Mongolian Tögrög"},
"MOP": currency{F, T, T, "P", "Macanese Pataca"},
"MRO": currency{F, T, T, "UM", "Mauritanian Ouguiya"},
"MTL": currency{T, T, T, "₤", "Maltese Lira"},
"MUR": currency{T, T, T, "₨", "Mauritian Rupee"},
"MVR": currency{F, T, T, "MVR", "Maldivian Rufiyaa"},
"MWK": currency{F, T, T, "MK", "Malawian Kwacha"},
"MXN": currency{T, T, T, "$", "Mexican Peso"},
"MYR": currency{T, T, T, "RM", "Malaysian Ringgit"},
"MZN": currency{T, F, T, "MTn", "Mozambican Metical"},
"NAD": currency{F, T, T, "$", "Namibian Dollar"},
"NGN": currency{T, T, T, "₦", "Nigerian Naira"},
"NIO": currency{F, T, T, "C$", "Nicaraguan Córdoba"},
"NOK": currency{F, F, T, "kr", "Norwegian Krone"},
"NPR": currency{T, T, T, "₨", "Nepalese Rupee"},
"NZD": currency{T, T, T, "$", "New Zealand Dollar"},
"OMR": currency{T, T, T, "ر.ع.", "Omani Rial"},
"PAB": currency{F, T, T, "B/.", "Panamanian Balboa"},
"PEN": currency{T, T, T, "S/.", "Peruvian Nuevo Sol"},
"PGK": currency{F, T, T, "K", "Papua New Guinean Kina"},
"PHP": currency{T, T, T, "₱", "Philippine Peso"},
"PKR": currency{T, T, T, "₨", "Pakistani Rupee"},
"PLN": currency{F, F, T, "zł", "Polish Złoty"},
"PYG": currency{T, T, T, "₲", "Paraguayan Guaraní"},
"QAR": currency{F, T, T, "ر.ق", "Qatari Riyal"},
"RON": currency{T, F, T, "Lei", "Romanian Leu"},
"RSD": currency{T, T, T, "РСД", "Serbian Dinar"},
"RUB": currency{F, F, F, "₽", "Russian Ruble"},
"RWF": currency{F, T, T, "FRw", "Rwandan Franc"},
"SAR": currency{T, T, T, "ر.س", "Saudi Riyal"},
"SBD": currency{F, T, T, "$", "Solomon Islands Dollar"},
"SCR": currency{F, T, T, "₨", "Seychellois Rupee"},
"SDG": currency{T, T, T, "£", "Sudanese Pound"},
"SEK": currency{F, F, T, "kr", "Swedish Krona"},
"SGD": currency{T, T, T, "$", "Singapore Dollar"},
"SHP": currency{F, T, T, "£", "Saint Helenian Pound"},
"SKK": currency{T, T, T, "Sk", "Slovak Koruna"},
"SLL": currency{F, T, T, "Le", "Sierra Leonean Leone"},
"SOS": currency{F, T, T, "Sh", "Somali Shilling"},
"SRD": currency{F, T, T, "$", "Surinamese Dollar"},
"SSP": currency{F, T, T, "£", "South Sudanese Pound"},
"STD": currency{F, T, T, "Db", "São Tomé and Príncipe Dobra"},
"SVC": currency{T, T, T, "₡", "Salvadoran Colón"},
"SYP": currency{F, T, T, "£S", "Syrian Pound"},
"SZL": currency{T, T, T, "L", "Swazi Lilangeni"},
"THB": currency{T, T, T, "฿", "Thai Baht"},
"TJS": currency{F, T, T, "ЅМ", "Tajikistani Somoni"},
"TMT": currency{F, T, T, "T", "Turkmenistani Manat"},
"TND": currency{F, T, T, "د.ت", "Tunisian Dinar"},
"TOP": currency{T, T, T, "T$", "Tongan Paʻanga"},
"TRY": currency{F, F, T, "₺", "Turkish Lira"},
"TTD": currency{F, T, T, "$", "Trinidad and Tobago Dollar"},
"TWD": currency{T, T, F, "$", "New Taiwan Dollar"},
"TZS": currency{T, T, T, "Sh", "Tanzanian Shilling"},
"UAH": currency{F, T, T, "₴", "Ukrainian Hryvnia"},
"UGX": currency{F, T, T, "USh", "Ugandan Shilling"},
"USD": currency{T, T, T, "$", "United States Dollar"},
"UYU": currency{T, F, F, "$", "Uruguayan Peso"},
"UZS": currency{F, T, T, "", "Uzbekistani Som"},
"VEF": currency{T, F, T, "Bs F", "Venezuelan Bolívar"},
"VND": currency{T, F, F, "₫", "Vietnamese Đồng"},
"VUV": currency{T, T, T, "Vt", "Vanuatu Vatu"},
"WST": currency{F, T, T, "T", "Samoan Tala"},
"XAF": currency{F, T, T, "Fr", "Central African Cfa Franc"},
"XAG": currency{F, T, T, "oz t", "Silver (Troy Ounce)"},
"XAU": currency{F, T, T, "oz t", "Gold (Troy Ounce)"},
"XCD": currency{T, T, T, "$", "East Caribbean Dollar"},
"XDR": currency{F, T, T, "SDR", "Special Drawing Rights"},
"XOF": currency{F, T, T, "Fr", "West African Cfa Franc"},
"XPF": currency{F, T, T, "Fr", "Cfp Franc"},
"YER": currency{F, T, T, "﷼", "Yemeni Rial"},
"ZAR": currency{T, T, T, "R", "South African Rand"},
"ZMK": currency{F, T, T, "ZK", "Zambian Kwacha"},
"ZMW": currency{F, T, T, "ZK", "Zambian Kwacha"},
"ZWD": currency{T, T, T, "$", "Zimbabwean Dollar"},
"ZWL": currency{T, T, T, "$", "Zimbabwean Dollar"},
"ZWN": currency{T, T, T, "$", "Zimbabwean Dollar"},
"ZWR": currency{T, T, T, "$", "Zimbabwean Dollar"},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment