1. Goal
Repeat Part 1 using FinMind (https://finmindtrade.com) as the data source. The final
dataset keeps only ordinary common stocks listed on the
Taiwan Stock Exchange (TWSE). ETFs, funds, preferred stocks and other
non-common securities are removed.
2. Load packages
# Install once (only if missing):
# install.packages(c("rmarkdown", "knitr", "httr2", "dplyr"))
library(httr2) # send the web request to the FinMind API
library(dplyr) # filter / clean / summarise data frames
library(knitr) # kable() prints nice tables in the HTML
3. Download the data from the FinMind API
FinMind’s dataset for Taiwan stock information is called
TaiwanStockInfo. Endpoint:
https://api.finmindtrade.com/api/v4/data, parameter
dataset=TaiwanStockInfo. Its columns are
industry_category, stock_id,
stock_name, type and date.
# A token is OPTIONAL. If you have one, set it in the R console (do NOT type it
# into this file): Sys.setenv(FINMIND_TOKEN = "your_token_here")
token <- Sys.getenv("FINMIND_TOKEN")
# Build the request
req <- request("https://api.finmindtrade.com/api/v4/data") |>
req_url_query(dataset = "TaiwanStockInfo") |>
req_timeout(60)
# Add the token only if the user has set one
if (nzchar(token)) req <- req_auth_bearer_token(req, token)
# Send the request. req_perform() stops with an error for HTTP errors (4xx/5xx),
# so we wrap it in tryCatch() to give a readable message.
resp <- tryCatch(
req_perform(req),
error = function(e) stop("FinMind request failed: ", conditionMessage(e))
)
# Convert the JSON body into R objects (data part becomes a data frame)
body <- resp_body_json(resp, simplifyVector = TRUE)
# Check the API-level status (FinMind returns status = 200 when OK)
if (is.null(body$status) || body$status != 200) {
stop("FinMind API error: ", body$msg)
}
if (is.null(body$data) || NROW(body$data) == 0) {
stop("FinMind returned an empty dataset.")
}
raw <- as_tibble(body$data)
# Check that the columns we need exist
needed <- c("stock_id", "stock_name", "industry_category", "type")
missing_cols <- setdiff(needed, names(raw))
if (length(missing_cols) > 0) {
stop("Missing expected columns: ", paste(missing_cols, collapse = ", "))
}
n_downloaded <- nrow(raw)
cat("Number of securities downloaded before filtering:", n_downloaded, "\n")
## Number of securities downloaded before filtering: 4340
4. Clean and filter (keep only ordinary common stocks)
# Step 0: make sure the key columns are text and remove extra spaces.
# stock_id MUST stay a character (text), never a number.
df <- raw |>
mutate(
stock_id = trimws(as.character(stock_id)),
stock_name = trimws(as.character(stock_name)),
industry_category = trimws(as.character(industry_category)),
type = tolower(trimws(as.character(type)))
)
# Categories that FinMind uses for things that are NOT ordinary stocks
non_common_categories <- c(
"ETF", "ETN", "Index", "\u5927\u76e4", # ETF, ETN, index, 大盤
"\u53d7\u76ca\u8b49\u5238", # 受益證券 (beneficiary certificates / funds)
"\u5b58\u8a17\u6191\u8b49", # 存託憑證 (depositary receipts)
"\u7279\u5225\u80a1", # 特別股 (preferred shares)
"\u6b0a\u8b49", "\u8a8d\u8cfc\u6b0a\u8b49", # 權證, 認購權證 (warrants)
"\u8a8d\u552e\u6b0a\u8b49", "\u725b\u718a\u8b49",
"\u7ba1\u7406\u80a1\u7968", "Holding", "Others"
)
# Keywords searched inside the CATEGORY text (a second safety net)
category_keywords <- "ETF|ETN|\u53d7\u76ca|\u5b58\u8a17|\u6b0a\u8b49|\u725b\u718a|\u7279\u5225\u80a1|\u6307\u6578"
# Each step removes rows; we count after every step for the report.
step1 <- df |> filter(type == "twse") # listed on TWSE only
step2 <- step1 |> filter(!is.na(stock_id), stock_id != "",
!is.na(stock_name), stock_name != "") # remove missing records
step3 <- step2 |> filter(!is.na(industry_category), industry_category != "",
!(industry_category %in% non_common_categories),
!grepl(category_keywords, industry_category, ignore.case = TRUE))
# Ordinary TWSE stocks have exactly 4 digits, first digit not 0.
# ETFs (e.g. 0050, 00878), preferred shares (e.g. 2882A) and warrants
# (5-6 characters) do not match this pattern.
step4 <- step3 |> filter(grepl("^[1-9][0-9]{3}$", stock_id))
# Remove duplicated stock codes (keep the most recently updated row)
step5 <- step4 |>
arrange(desc(date)) |>
distinct(stock_id, .keep_all = TRUE)
# Summary of what each step removed
filter_log <- tibble(
step = c("Downloaded", "Only type = twse", "Remove missing records",
"Remove non-common categories (ETF/fund/DR/preferred/...)",
"Keep 4-digit numeric codes only", "Remove duplicate stock_id"),
rows_remaining = c(nrow(df), nrow(step1), nrow(step2),
nrow(step3), nrow(step4), nrow(step5))
) |>
mutate(rows_removed = lag(rows_remaining, default = first(rows_remaining)) - rows_remaining)
kable(filter_log, caption = "Rows remaining / removed at each filtering step")
Rows remaining / removed at each filtering step
| Downloaded |
4340 |
0 |
| Only type = twse |
2404 |
1936 |
| Remove missing records |
2404 |
0 |
| Remove non-common categories
(ETF/fund/DR/preferred/…) |
2029 |
375 |
| Keep 4-digit numeric codes only |
1989 |
40 |
| Remove duplicate stock_id |
1216 |
773 |
5. Build the final dataset
FinMind’s TaiwanStockInfo does not
contain the original listing date. Its date column is only
the date FinMind updated the record, so it must not be
treated as a listing date. The code uses a listing-date column only if
FinMind provides one; otherwise listing_date is left as
NA.
# Look for a real listing-date column in the FinMind data (none expected)
date_col <- intersect(c("listing_date", "list_date"), names(step5))
final <- step5 |>
transmute(
stock_id,
name = stock_name,
listing_date = if (length(date_col) > 0) as.Date(.data[[date_col[1]]]) else as.Date(NA),
industry = industry_category,
finmind_update_date = as.Date(date) # for reference only, NOT the listing date
) |>
arrange(stock_id)
n_final <- nrow(final)
cat("Securities downloaded before filtering:", n_downloaded, "\n")
## Securities downloaded before filtering: 4340
cat("Common stocks remaining after filtering:", n_final, "\n")
## Common stocks remaining after filtering: 1216
cat("Records removed:", n_downloaded - n_final, "\n")
## Records removed: 3124
cat("Rows with missing listing_date:", sum(is.na(final$listing_date)), "\n")
## Rows with missing listing_date: 1216
First 20 rows
kable(head(final, 20))
| 1101 |
台泥 |
NA |
水泥工業 |
2026-10-16 |
| 1102 |
亞泥 |
NA |
水泥工業 |
2026-10-16 |
| 1103 |
嘉泥 |
NA |
水泥工業 |
2026-10-16 |
| 1104 |
環泥 |
NA |
水泥工業 |
2026-10-16 |
| 1107 |
建台 |
NA |
其他 |
2024-12-04 |
| 1108 |
幸福 |
NA |
水泥工業 |
2026-10-16 |
| 1109 |
信大 |
NA |
水泥工業 |
2026-10-16 |
| 1110 |
東泥 |
NA |
水泥工業 |
2026-10-16 |
| 1201 |
味全 |
NA |
食品工業 |
2026-10-16 |
| 1203 |
味王 |
NA |
食品工業 |
2026-10-16 |
| 1210 |
大成 |
NA |
食品工業 |
2026-10-16 |
| 1213 |
大飲 |
NA |
食品工業 |
2026-10-16 |
| 1215 |
卜蜂 |
NA |
食品工業 |
2026-10-16 |
| 1216 |
統一 |
NA |
食品工業 |
2026-10-16 |
| 1217 |
愛之味 |
NA |
食品工業 |
2026-10-16 |
| 1218 |
泰山 |
NA |
食品工業 |
2026-10-16 |
| 1219 |
福壽 |
NA |
食品工業 |
2026-10-16 |
| 1220 |
台榮 |
NA |
食品工業 |
2026-10-16 |
| 1225 |
福懋油 |
NA |
食品工業 |
2026-10-16 |
| 1227 |
佳格 |
NA |
食品工業 |
2026-10-16 |
Sanity check
If the filters worked, this table should be empty (no
ETF/fund/preferred/DR wording left in the names, and no stock code that
is not 4 digits).
suspicious <- final |>
filter(grepl("ETF|ETN|\u57fa\u91d1|\u7279\u5225\u80a1|-DR", name, ignore.case = TRUE) |
!grepl("^[1-9][0-9]{3}$", stock_id))
kable(suspicious, caption = "Possible leftovers (should be empty)")
Possible leftovers (should be empty)
| 2883 |
凱基金 |
NA |
金融保險 |
2026-10-16 |
6. Number of stocks by industry
industry_summary <- final |>
count(industry, name = "n_stocks", sort = TRUE)
kable(industry_summary)
| 電子工業 |
258 |
| 半導體業 |
75 |
| 光電業 |
62 |
| 其他 |
62 |
| 化學生技醫療 |
57 |
| 建材營造 |
57 |
| 電機機械 |
51 |
| 金融保險 |
46 |
| 紡織纖維 |
43 |
| 汽車工業 |
42 |
| 其他電子業 |
39 |
| 通信網路業 |
35 |
| 鋼鐵工業 |
31 |
| 航運業 |
29 |
| 創新板股票 |
26 |
| 化學工業 |
25 |
| 食品工業 |
24 |
| 塑膠工業 |
23 |
| 綠能環保 |
23 |
| 電腦及週邊設備業 |
22 |
| 貿易百貨 |
20 |
| 電器電纜 |
20 |
| 觀光餐旅 |
19 |
| 電子零組件業 |
19 |
| 運動休閒 |
18 |
| 居家生活 |
11 |
| 數位雲端 |
11 |
| 橡膠工業 |
11 |
| 生技醫療業 |
11 |
| 油電燃氣業 |
9 |
| 電子通路業 |
9 |
| 資訊服務業 |
8 |
| 造紙工業 |
8 |
| 水泥工業 |
7 |
| 玻璃陶瓷 |
5 |
7. Save the cleaned dataset
# stock_id is text, so leading zeros (if any) are preserved.
write.csv(final, "finmind_tw_listed_stocks.csv",
row.names = FALSE, na = "", fileEncoding = "UTF-8")
cat("Saved to:", normalizePath("finmind_tw_listed_stocks.csv"), "\n")
## Saved to: C:\Users\Dell\Downloads\finmind_tw_listed_stocks.csv
8. Limitations
- FinMind
TaiwanStockInfo has no original listing date.
The date column is an update date, so
listing_date is NA unless FinMind adds such a
column.
- “Listed” means
type == "twse" (TWSE main board). OTC
(tpex) stocks are excluded.
- Filtering relies on FinMind’s
industry_category plus
the 4-digit code format. FinMind may rename categories in the future, so
check the sanity table above.
LS0tDQp0aXRsZTogIkhvbWUgd29yayA0Ig0KYXV0aG9yOiAiMTE1MDM1MTE4Ig0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgU2hvdyBjb2RlIGFuZCByZXN1bHRzIGluIHRoZSBIVE1MOyBrZWVwIHdhcm5pbmdzIG91dCBvZiB0aGUgcmVwb3J0DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KYGBgDQoNCiMgMS4gR29hbA0KDQpSZXBlYXQgUGFydCAxIHVzaW5nICoqRmluTWluZCoqIChodHRwczovL2Zpbm1pbmR0cmFkZS5jb20pIGFzIHRoZSBkYXRhIHNvdXJjZS4NClRoZSBmaW5hbCBkYXRhc2V0IGtlZXBzIG9ubHkgKipvcmRpbmFyeSBjb21tb24gc3RvY2tzKiogbGlzdGVkIG9uIHRoZSBUYWl3YW4NClN0b2NrIEV4Y2hhbmdlIChUV1NFKS4gRVRGcywgZnVuZHMsIHByZWZlcnJlZCBzdG9ja3MgYW5kIG90aGVyIG5vbi1jb21tb24NCnNlY3VyaXRpZXMgYXJlIHJlbW92ZWQuDQoNCiMgMi4gTG9hZCBwYWNrYWdlcw0KDQpgYGB7ciBwYWNrYWdlc30NCiMgSW5zdGFsbCBvbmNlIChvbmx5IGlmIG1pc3NpbmcpOg0KIyBpbnN0YWxsLnBhY2thZ2VzKGMoInJtYXJrZG93biIsICJrbml0ciIsICJodHRyMiIsICJkcGx5ciIpKQ0KbGlicmFyeShodHRyMikgICAjIHNlbmQgdGhlIHdlYiByZXF1ZXN0IHRvIHRoZSBGaW5NaW5kIEFQSQ0KbGlicmFyeShkcGx5cikgICAjIGZpbHRlciAvIGNsZWFuIC8gc3VtbWFyaXNlIGRhdGEgZnJhbWVzDQpsaWJyYXJ5KGtuaXRyKSAgICMga2FibGUoKSBwcmludHMgbmljZSB0YWJsZXMgaW4gdGhlIEhUTUwNCmBgYA0KDQojIDMuIERvd25sb2FkIHRoZSBkYXRhIGZyb20gdGhlIEZpbk1pbmQgQVBJDQoNCkZpbk1pbmQncyBkYXRhc2V0IGZvciBUYWl3YW4gc3RvY2sgaW5mb3JtYXRpb24gaXMgY2FsbGVkICoqYFRhaXdhblN0b2NrSW5mb2AqKi4NCkVuZHBvaW50OiBgaHR0cHM6Ly9hcGkuZmlubWluZHRyYWRlLmNvbS9hcGkvdjQvZGF0YWAsIHBhcmFtZXRlciBgZGF0YXNldD1UYWl3YW5TdG9ja0luZm9gLg0KSXRzIGNvbHVtbnMgYXJlIGBpbmR1c3RyeV9jYXRlZ29yeWAsIGBzdG9ja19pZGAsIGBzdG9ja19uYW1lYCwgYHR5cGVgIGFuZCBgZGF0ZWAuDQoNCmBgYHtyIGRvd25sb2FkfQ0KIyBBIHRva2VuIGlzIE9QVElPTkFMLiBJZiB5b3UgaGF2ZSBvbmUsIHNldCBpdCBpbiB0aGUgUiBjb25zb2xlIChkbyBOT1QgdHlwZSBpdA0KIyBpbnRvIHRoaXMgZmlsZSk6ICBTeXMuc2V0ZW52KEZJTk1JTkRfVE9LRU4gPSAieW91cl90b2tlbl9oZXJlIikNCnRva2VuIDwtIFN5cy5nZXRlbnYoIkZJTk1JTkRfVE9LRU4iKQ0KDQojIEJ1aWxkIHRoZSByZXF1ZXN0DQpyZXEgPC0gcmVxdWVzdCgiaHR0cHM6Ly9hcGkuZmlubWluZHRyYWRlLmNvbS9hcGkvdjQvZGF0YSIpIHw+DQogIHJlcV91cmxfcXVlcnkoZGF0YXNldCA9ICJUYWl3YW5TdG9ja0luZm8iKSB8Pg0KICByZXFfdGltZW91dCg2MCkNCg0KIyBBZGQgdGhlIHRva2VuIG9ubHkgaWYgdGhlIHVzZXIgaGFzIHNldCBvbmUNCmlmIChuemNoYXIodG9rZW4pKSByZXEgPC0gcmVxX2F1dGhfYmVhcmVyX3Rva2VuKHJlcSwgdG9rZW4pDQoNCiMgU2VuZCB0aGUgcmVxdWVzdC4gcmVxX3BlcmZvcm0oKSBzdG9wcyB3aXRoIGFuIGVycm9yIGZvciBIVFRQIGVycm9ycyAoNHh4LzV4eCksDQojIHNvIHdlIHdyYXAgaXQgaW4gdHJ5Q2F0Y2goKSB0byBnaXZlIGEgcmVhZGFibGUgbWVzc2FnZS4NCnJlc3AgPC0gdHJ5Q2F0Y2goDQogIHJlcV9wZXJmb3JtKHJlcSksDQogIGVycm9yID0gZnVuY3Rpb24oZSkgc3RvcCgiRmluTWluZCByZXF1ZXN0IGZhaWxlZDogIiwgY29uZGl0aW9uTWVzc2FnZShlKSkNCikNCg0KIyBDb252ZXJ0IHRoZSBKU09OIGJvZHkgaW50byBSIG9iamVjdHMgKGRhdGEgcGFydCBiZWNvbWVzIGEgZGF0YSBmcmFtZSkNCmJvZHkgPC0gcmVzcF9ib2R5X2pzb24ocmVzcCwgc2ltcGxpZnlWZWN0b3IgPSBUUlVFKQ0KDQojIENoZWNrIHRoZSBBUEktbGV2ZWwgc3RhdHVzIChGaW5NaW5kIHJldHVybnMgc3RhdHVzID0gMjAwIHdoZW4gT0spDQppZiAoaXMubnVsbChib2R5JHN0YXR1cykgfHwgYm9keSRzdGF0dXMgIT0gMjAwKSB7DQogIHN0b3AoIkZpbk1pbmQgQVBJIGVycm9yOiAiLCBib2R5JG1zZykNCn0NCmlmIChpcy5udWxsKGJvZHkkZGF0YSkgfHwgTlJPVyhib2R5JGRhdGEpID09IDApIHsNCiAgc3RvcCgiRmluTWluZCByZXR1cm5lZCBhbiBlbXB0eSBkYXRhc2V0LiIpDQp9DQoNCnJhdyA8LSBhc190aWJibGUoYm9keSRkYXRhKQ0KDQojIENoZWNrIHRoYXQgdGhlIGNvbHVtbnMgd2UgbmVlZCBleGlzdA0KbmVlZGVkIDwtIGMoInN0b2NrX2lkIiwgInN0b2NrX25hbWUiLCAiaW5kdXN0cnlfY2F0ZWdvcnkiLCAidHlwZSIpDQptaXNzaW5nX2NvbHMgPC0gc2V0ZGlmZihuZWVkZWQsIG5hbWVzKHJhdykpDQppZiAobGVuZ3RoKG1pc3NpbmdfY29scykgPiAwKSB7DQogIHN0b3AoIk1pc3NpbmcgZXhwZWN0ZWQgY29sdW1uczogIiwgcGFzdGUobWlzc2luZ19jb2xzLCBjb2xsYXBzZSA9ICIsICIpKQ0KfQ0KDQpuX2Rvd25sb2FkZWQgPC0gbnJvdyhyYXcpDQpjYXQoIk51bWJlciBvZiBzZWN1cml0aWVzIGRvd25sb2FkZWQgYmVmb3JlIGZpbHRlcmluZzoiLCBuX2Rvd25sb2FkZWQsICJcbiIpDQpgYGANCg0KIyA0LiBDbGVhbiBhbmQgZmlsdGVyIChrZWVwIG9ubHkgb3JkaW5hcnkgY29tbW9uIHN0b2NrcykNCg0KYGBge3IgZmlsdGVyfQ0KIyBTdGVwIDA6IG1ha2Ugc3VyZSB0aGUga2V5IGNvbHVtbnMgYXJlIHRleHQgYW5kIHJlbW92ZSBleHRyYSBzcGFjZXMuDQojIHN0b2NrX2lkIE1VU1Qgc3RheSBhIGNoYXJhY3RlciAodGV4dCksIG5ldmVyIGEgbnVtYmVyLg0KZGYgPC0gcmF3IHw+DQogIG11dGF0ZSgNCiAgICBzdG9ja19pZCAgICAgICAgICA9IHRyaW13cyhhcy5jaGFyYWN0ZXIoc3RvY2tfaWQpKSwNCiAgICBzdG9ja19uYW1lICAgICAgICA9IHRyaW13cyhhcy5jaGFyYWN0ZXIoc3RvY2tfbmFtZSkpLA0KICAgIGluZHVzdHJ5X2NhdGVnb3J5ID0gdHJpbXdzKGFzLmNoYXJhY3RlcihpbmR1c3RyeV9jYXRlZ29yeSkpLA0KICAgIHR5cGUgICAgICAgICAgICAgID0gdG9sb3dlcih0cmltd3MoYXMuY2hhcmFjdGVyKHR5cGUpKSkNCiAgKQ0KDQojIENhdGVnb3JpZXMgdGhhdCBGaW5NaW5kIHVzZXMgZm9yIHRoaW5ncyB0aGF0IGFyZSBOT1Qgb3JkaW5hcnkgc3RvY2tzDQpub25fY29tbW9uX2NhdGVnb3JpZXMgPC0gYygNCiAgIkVURiIsICJFVE4iLCAiSW5kZXgiLCAiXHU1OTI3XHU3NmU0IiwgICAgICAgICAgIyBFVEYsIEVUTiwgaW5kZXgsIOWkp+ebpA0KICAiXHU1M2Q3XHU3NmNhXHU4YjQ5XHU1MjM4IiwgICAgICAgICAgICAgICAgICAgICAjIOWPl+ebiuitieWIuCAoYmVuZWZpY2lhcnkgY2VydGlmaWNhdGVzIC8gZnVuZHMpDQogICJcdTViNThcdThhMTdcdTYxOTFcdThiNDkiLCAgICAgICAgICAgICAgICAgICAgICMg5a2Y6KiX5oaR6K2JIChkZXBvc2l0YXJ5IHJlY2VpcHRzKQ0KICAiXHU3Mjc5XHU1MjI1XHU4MGExIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIOeJueWIpeiCoSAocHJlZmVycmVkIHNoYXJlcykNCiAgIlx1NmIwYVx1OGI0OSIsICJcdThhOGRcdThjZmNcdTZiMGFcdThiNDkiLCAgICAgIyDmrIrorYksIOiqjeizvOasiuitiSAod2FycmFudHMpDQogICJcdThhOGRcdTU1MmVcdTZiMGFcdThiNDkiLCAiXHU3MjViXHU3MThhXHU4YjQ5IiwNCiAgIlx1N2JhMVx1NzQwNlx1ODBhMVx1Nzk2OCIsICJIb2xkaW5nIiwgIk90aGVycyINCikNCiMgS2V5d29yZHMgc2VhcmNoZWQgaW5zaWRlIHRoZSBDQVRFR09SWSB0ZXh0IChhIHNlY29uZCBzYWZldHkgbmV0KQ0KY2F0ZWdvcnlfa2V5d29yZHMgPC0gIkVURnxFVE58XHU1M2Q3XHU3NmNhfFx1NWI1OFx1OGExN3xcdTZiMGFcdThiNDl8XHU3MjViXHU3MThhfFx1NzI3OVx1NTIyNVx1ODBhMXxcdTYzMDdcdTY1NzgiDQoNCiMgRWFjaCBzdGVwIHJlbW92ZXMgcm93czsgd2UgY291bnQgYWZ0ZXIgZXZlcnkgc3RlcCBmb3IgdGhlIHJlcG9ydC4NCnN0ZXAxIDwtIGRmICAgIHw+IGZpbHRlcih0eXBlID09ICJ0d3NlIikgICAgICAgICAgICAgICAgICAgICAgICMgbGlzdGVkIG9uIFRXU0Ugb25seQ0Kc3RlcDIgPC0gc3RlcDEgfD4gZmlsdGVyKCFpcy5uYShzdG9ja19pZCksIHN0b2NrX2lkICE9ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShzdG9ja19uYW1lKSwgc3RvY2tfbmFtZSAhPSAiIikgIyByZW1vdmUgbWlzc2luZyByZWNvcmRzDQpzdGVwMyA8LSBzdGVwMiB8PiBmaWx0ZXIoIWlzLm5hKGluZHVzdHJ5X2NhdGVnb3J5KSwgaW5kdXN0cnlfY2F0ZWdvcnkgIT0gIiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIShpbmR1c3RyeV9jYXRlZ29yeSAlaW4lIG5vbl9jb21tb25fY2F0ZWdvcmllcyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIWdyZXBsKGNhdGVnb3J5X2tleXdvcmRzLCBpbmR1c3RyeV9jYXRlZ29yeSwgaWdub3JlLmNhc2UgPSBUUlVFKSkNCiMgT3JkaW5hcnkgVFdTRSBzdG9ja3MgaGF2ZSBleGFjdGx5IDQgZGlnaXRzLCBmaXJzdCBkaWdpdCBub3QgMC4NCiMgRVRGcyAoZS5nLiAwMDUwLCAwMDg3OCksIHByZWZlcnJlZCBzaGFyZXMgKGUuZy4gMjg4MkEpIGFuZCB3YXJyYW50cw0KIyAoNS02IGNoYXJhY3RlcnMpIGRvIG5vdCBtYXRjaCB0aGlzIHBhdHRlcm4uDQpzdGVwNCA8LSBzdGVwMyB8PiBmaWx0ZXIoZ3JlcGwoIl5bMS05XVswLTldezN9JCIsIHN0b2NrX2lkKSkNCg0KIyBSZW1vdmUgZHVwbGljYXRlZCBzdG9jayBjb2RlcyAoa2VlcCB0aGUgbW9zdCByZWNlbnRseSB1cGRhdGVkIHJvdykNCnN0ZXA1IDwtIHN0ZXA0IHw+DQogIGFycmFuZ2UoZGVzYyhkYXRlKSkgfD4NCiAgZGlzdGluY3Qoc3RvY2tfaWQsIC5rZWVwX2FsbCA9IFRSVUUpDQoNCiMgU3VtbWFyeSBvZiB3aGF0IGVhY2ggc3RlcCByZW1vdmVkDQpmaWx0ZXJfbG9nIDwtIHRpYmJsZSgNCiAgc3RlcCA9IGMoIkRvd25sb2FkZWQiLCAiT25seSB0eXBlID0gdHdzZSIsICJSZW1vdmUgbWlzc2luZyByZWNvcmRzIiwNCiAgICAgICAgICAgIlJlbW92ZSBub24tY29tbW9uIGNhdGVnb3JpZXMgKEVURi9mdW5kL0RSL3ByZWZlcnJlZC8uLi4pIiwNCiAgICAgICAgICAgIktlZXAgNC1kaWdpdCBudW1lcmljIGNvZGVzIG9ubHkiLCAiUmVtb3ZlIGR1cGxpY2F0ZSBzdG9ja19pZCIpLA0KICByb3dzX3JlbWFpbmluZyA9IGMobnJvdyhkZiksIG5yb3coc3RlcDEpLCBucm93KHN0ZXAyKSwNCiAgICAgICAgICAgICAgICAgICAgIG5yb3coc3RlcDMpLCBucm93KHN0ZXA0KSwgbnJvdyhzdGVwNSkpDQopIHw+DQogIG11dGF0ZShyb3dzX3JlbW92ZWQgPSBsYWcocm93c19yZW1haW5pbmcsIGRlZmF1bHQgPSBmaXJzdChyb3dzX3JlbWFpbmluZykpIC0gcm93c19yZW1haW5pbmcpDQoNCmthYmxlKGZpbHRlcl9sb2csIGNhcHRpb24gPSAiUm93cyByZW1haW5pbmcgLyByZW1vdmVkIGF0IGVhY2ggZmlsdGVyaW5nIHN0ZXAiKQ0KYGBgDQoNCiMgNS4gQnVpbGQgdGhlIGZpbmFsIGRhdGFzZXQNCg0KRmluTWluZCdzIGBUYWl3YW5TdG9ja0luZm9gIGRvZXMgKipub3QqKiBjb250YWluIHRoZSBvcmlnaW5hbCBsaXN0aW5nIGRhdGUuIEl0cw0KYGRhdGVgIGNvbHVtbiBpcyBvbmx5IHRoZSBkYXRlIEZpbk1pbmQgdXBkYXRlZCB0aGUgcmVjb3JkLCBzbyBpdCBtdXN0ICoqbm90KioNCmJlIHRyZWF0ZWQgYXMgYSBsaXN0aW5nIGRhdGUuIFRoZSBjb2RlIHVzZXMgYSBsaXN0aW5nLWRhdGUgY29sdW1uIG9ubHkgaWYNCkZpbk1pbmQgcHJvdmlkZXMgb25lOyBvdGhlcndpc2UgYGxpc3RpbmdfZGF0ZWAgaXMgbGVmdCBhcyBgTkFgLg0KDQpgYGB7ciBmaW5hbH0NCiMgTG9vayBmb3IgYSByZWFsIGxpc3RpbmctZGF0ZSBjb2x1bW4gaW4gdGhlIEZpbk1pbmQgZGF0YSAobm9uZSBleHBlY3RlZCkNCmRhdGVfY29sIDwtIGludGVyc2VjdChjKCJsaXN0aW5nX2RhdGUiLCAibGlzdF9kYXRlIiksIG5hbWVzKHN0ZXA1KSkNCg0KZmluYWwgPC0gc3RlcDUgfD4NCiAgdHJhbnNtdXRlKA0KICAgIHN0b2NrX2lkLA0KICAgIG5hbWUgPSBzdG9ja19uYW1lLA0KICAgIGxpc3RpbmdfZGF0ZSA9IGlmIChsZW5ndGgoZGF0ZV9jb2wpID4gMCkgYXMuRGF0ZSguZGF0YVtbZGF0ZV9jb2xbMV1dXSkgZWxzZSBhcy5EYXRlKE5BKSwNCiAgICBpbmR1c3RyeSA9IGluZHVzdHJ5X2NhdGVnb3J5LA0KICAgIGZpbm1pbmRfdXBkYXRlX2RhdGUgPSBhcy5EYXRlKGRhdGUpICAgIyBmb3IgcmVmZXJlbmNlIG9ubHksIE5PVCB0aGUgbGlzdGluZyBkYXRlDQogICkgfD4NCiAgYXJyYW5nZShzdG9ja19pZCkNCg0Kbl9maW5hbCA8LSBucm93KGZpbmFsKQ0KY2F0KCJTZWN1cml0aWVzIGRvd25sb2FkZWQgYmVmb3JlIGZpbHRlcmluZzoiLCBuX2Rvd25sb2FkZWQsICJcbiIpDQpjYXQoIkNvbW1vbiBzdG9ja3MgcmVtYWluaW5nIGFmdGVyIGZpbHRlcmluZzoiLCBuX2ZpbmFsLCAiXG4iKQ0KY2F0KCJSZWNvcmRzIHJlbW92ZWQ6Iiwgbl9kb3dubG9hZGVkIC0gbl9maW5hbCwgIlxuIikNCmNhdCgiUm93cyB3aXRoIG1pc3NpbmcgbGlzdGluZ19kYXRlOiIsIHN1bShpcy5uYShmaW5hbCRsaXN0aW5nX2RhdGUpKSwgIlxuIikNCmBgYA0KDQojIyBGaXJzdCAyMCByb3dzDQoNCmBgYHtyIGhlYWR9DQprYWJsZShoZWFkKGZpbmFsLCAyMCkpDQpgYGANCg0KIyMgU2FuaXR5IGNoZWNrDQoNCklmIHRoZSBmaWx0ZXJzIHdvcmtlZCwgdGhpcyB0YWJsZSBzaG91bGQgYmUgZW1wdHkgKG5vIEVURi9mdW5kL3ByZWZlcnJlZC9EUg0Kd29yZGluZyBsZWZ0IGluIHRoZSBuYW1lcywgYW5kIG5vIHN0b2NrIGNvZGUgdGhhdCBpcyBub3QgNCBkaWdpdHMpLg0KDQpgYGB7ciBjaGVja30NCnN1c3BpY2lvdXMgPC0gZmluYWwgfD4NCiAgZmlsdGVyKGdyZXBsKCJFVEZ8RVROfFx1NTdmYVx1OTFkMXxcdTcyNzlcdTUyMjVcdTgwYTF8LURSIiwgbmFtZSwgaWdub3JlLmNhc2UgPSBUUlVFKSB8DQogICAgICAgICAhZ3JlcGwoIl5bMS05XVswLTldezN9JCIsIHN0b2NrX2lkKSkNCmthYmxlKHN1c3BpY2lvdXMsIGNhcHRpb24gPSAiUG9zc2libGUgbGVmdG92ZXJzIChzaG91bGQgYmUgZW1wdHkpIikNCmBgYA0KDQojIDYuIE51bWJlciBvZiBzdG9ja3MgYnkgaW5kdXN0cnkNCg0KYGBge3IgaW5kdXN0cnl9DQppbmR1c3RyeV9zdW1tYXJ5IDwtIGZpbmFsIHw+DQogIGNvdW50KGluZHVzdHJ5LCBuYW1lID0gIm5fc3RvY2tzIiwgc29ydCA9IFRSVUUpDQprYWJsZShpbmR1c3RyeV9zdW1tYXJ5KQ0KYGBgDQoNCiMgNy4gU2F2ZSB0aGUgY2xlYW5lZCBkYXRhc2V0DQoNCmBgYHtyIHNhdmV9DQojIHN0b2NrX2lkIGlzIHRleHQsIHNvIGxlYWRpbmcgemVyb3MgKGlmIGFueSkgYXJlIHByZXNlcnZlZC4NCndyaXRlLmNzdihmaW5hbCwgImZpbm1pbmRfdHdfbGlzdGVkX3N0b2Nrcy5jc3YiLA0KICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFLCBuYSA9ICIiLCBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKQ0KY2F0KCJTYXZlZCB0bzoiLCBub3JtYWxpemVQYXRoKCJmaW5taW5kX3R3X2xpc3RlZF9zdG9ja3MuY3N2IiksICJcbiIpDQpgYGANCg0KIyA4LiBMaW1pdGF0aW9ucw0KDQoqIEZpbk1pbmQgYFRhaXdhblN0b2NrSW5mb2AgaGFzIG5vIG9yaWdpbmFsIGxpc3RpbmcgZGF0ZS4gVGhlIGBkYXRlYCBjb2x1bW4gaXMgYW4NCiAgdXBkYXRlIGRhdGUsIHNvIGBsaXN0aW5nX2RhdGVgIGlzIGBOQWAgdW5sZXNzIEZpbk1pbmQgYWRkcyBzdWNoIGEgY29sdW1uLg0KKiAiTGlzdGVkIiBtZWFucyBgdHlwZSA9PSAidHdzZSJgIChUV1NFIG1haW4gYm9hcmQpLiBPVEMgKGB0cGV4YCkgc3RvY2tzIGFyZSBleGNsdWRlZC4NCiogRmlsdGVyaW5nIHJlbGllcyBvbiBGaW5NaW5kJ3MgYGluZHVzdHJ5X2NhdGVnb3J5YCBwbHVzIHRoZSA0LWRpZ2l0IGNvZGUgZm9ybWF0Lg0KICBGaW5NaW5kIG1heSByZW5hbWUgY2F0ZWdvcmllcyBpbiB0aGUgZnV0dXJlLCBzbyBjaGVjayB0aGUgc2FuaXR5IHRhYmxlIGFib3ZlLg0K