Created
December 24, 2017 14:38
Steam-Discount-Sticker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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