Search

Java - PTT 文章代碼(AID)轉換為 URL

2015-12-26 5:38 PM

因為開發經驗第二集要花比較多時間撰寫

剛好最近有用到 AID(文章代碼) 轉換成 URL 網址的方法

寫了一個轉換工具 AidConverter

可以轉換 AID 為 URL 或是 檔案名稱

也可以將 URL 或 檔案名稱 轉換為 AID

就決定先寫這篇教學文章了

PTT 的原始碼是用 C 語言寫的 而且檔名很可怕!!!

但我只碰過 C++ 而且那也已經是N年前的事了(遙想當年...)

我懶得複習也懶得去看那幾段程式碼到底在做什麼

就用硬轉的方式把那幾段轉成 Java 語言

所以這段教學不會有什麼說明 打算放放原始碼就了事...

雖然測試回傳結果都是正確的 但只測過十幾篇所以記得自己加一些錯誤處理

如果有大師願意修正為更好的版本也請不要客氣囉~

AidConverter
  1. import com.google.common.base.Splitter;
  2. import com.google.common.collect.BiMap;
  3. import com.google.common.collect.HashBiMap;
  4. import com.ihad.ptt.model.bean.AidBean;
  5. import org.apache.commons.lang3.StringUtils;
  6.  
  7. import java.util.List;
  8.  
  9. /**
  10. * 文章編號與檔案名稱轉換工具<br />
  11. * 將符合此格式的 [a-zA-Z-_]{8} 文章編號轉換為檔案名稱 或是 URL<br />
  12. * 範例:<br />
  13. * 1MVYyFDv -> M.1451110159.A.379 -> https://www.ptt.cc/bbs/Gossiping/M.1451110159.A.379.html<br /><br />
  14. * 或是將符合 [M|G].[unsigned_integer].A.[HEX{3}] 格式的檔案名稱轉換為文章編號<br />
  15. * 若為 URL 則將轉換為 {@link AidBean}, 其中包含看板名與文章編號<br />
  16. * 範例:<br />
  17. * https://www.ptt.cc/bbs/Gossiping/M.1451110159.A.379.html -> M.1451110159.A.379 -> 1MVYyFDv
  18. */
  19. public class AidConverter {
  20.  
  21. private static final String DOMAIN_URL = "https://www.ptt.cc/bbs/";
  22. private static final String FILE_EXT = ".html";
  23.  
  24. private static String aidTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
  25. private static BiMap<Character, Long> table = tableInitializer();
  26.  
  27. /**
  28. * 建立文章編號字元 Map, 方便取得對應數值
  29. * @return
  30. * 文章編號字元 Map
  31. */
  32. private static BiMap<Character, Long> tableInitializer(){
  33. BiMap<Character, Long> table = HashBiMap.create();
  34.  
  35. long index = 0;
  36. int size = aidTable.length();
  37.  
  38. for( int i = 0; i < size; i++ ){
  39. table.forcePut( aidTable.charAt(i), index );
  40. index ++;
  41. }
  42.  
  43. return table;
  44. }
  45.  
  46. /**
  47. * 將檔案名稱轉換為˙數字型態的文章序號
  48. * @param fn 檔案名稱
  49. * @return
  50. * 數字型態的文章序號, 若檔案名稱格式不符將回傳 0<br />
  51. * 轉換後的文章編號將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
  52. * 範例: M.1451100858.A.71E
  53. */
  54. private static long fn2aidu(String fn){
  55. long aidu = 0;
  56. long type = 0;
  57. long v1 = 0;
  58. long v2 = 0;
  59.  
  60. if( fn == null ) return 0;
  61.  
  62. List<String> fnList = Splitter.on(".").omitEmptyStrings().trimResults().splitToList(fn);
  63.  
  64. if( fnList.size() != 4 ) return 0;
  65.  
  66. String typeString = fnList.get(0);
  67. String v1String = fnList.get(1);
  68. String v2String = fnList.get(3);
  69.  
  70. if( !fnList.get(2).equals("A") ) return 0;
  71. if( !StringUtils.isNumeric(v1String) || v1String.length() != 10 ) return 0;
  72.  
  73. switch (typeString){
  74. case "M":
  75. type = 0;
  76. break;
  77. case "G":
  78. type = 1;
  79. break;
  80. default:
  81. return 0;
  82. }
  83.  
  84. v1 = Long.parseLong( v1String );
  85. v2 = Long.parseLong( v2String, 16 );
  86.  
  87. aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffffL) << 12) | (v2 & 0xfff);
  88.  
  89. return aidu;
  90. }
  91.  
  92. /**
  93. * 將數字型態的文章序號轉換為字串型態的文章編號
  94. * @param aidu 數字型態之文章序號
  95. * @return
  96. * 轉換後的文章編號將符合 [a-zA-Z-_]{8}<br />
  97. * 範例: 1MVWgwSU
  98. */
  99. private static String aidu2aidc(long aidu){
  100. int size = table.size();
  101. BiMap<Long, Character> inverseTable = table.inverse();
  102.  
  103. StringBuffer stringBuffer = new StringBuffer();
  104.  
  105. while( stringBuffer.length() < 8 ){
  106. long v = aidu % size;
  107. if( !inverseTable.containsKey( v ) ) return null;
  108.  
  109. stringBuffer.insert( 0, inverseTable.get( v ) );
  110. aidu = aidu / size;
  111. }
  112.  
  113. return stringBuffer.toString();
  114. }
  115.  
  116. /**
  117. * 將文章編號轉換為數字型態的文章序號
  118. * @param aid 文章編號
  119. * @return
  120. * 數字型態的文章序號
  121. */
  122. private static long aidc2aidu(String aid){
  123. char[] aidChars = aid.toCharArray();
  124. long aidu = 0;
  125.  
  126. for( char aidChar : aidChars ){
  127. if( aidChar == '@' ) break;
  128. if( !table.containsKey(aidChar) ) return 0;
  129.  
  130. long v = table.get(aidChar);
  131.  
  132. aidu = aidu << 6;
  133. aidu = aidu | (v & 0x3f);
  134. }
  135.  
  136. return aidu;
  137. }
  138.  
  139. /**
  140. * 將文章序號(數字型態)轉換為檔案名稱
  141. * @param aidu 文章序號(數字型態)
  142. * @return
  143. * 轉換後的檔案名稱, 格式將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
  144. * 最後的16進位表示法若未滿3個字將以0從左邊開始補齊<br />
  145. * 範例: M.1451100858.A.71E
  146. */
  147. private static String aidu2fn(long aidu){
  148. long type = ((aidu >> 44) & 0xf);
  149. long v1 = ((aidu >> 12) & 0xffffffffL);
  150. long v2 = (aidu & 0xfff);
  151.  
  152. // casting to unsigned
  153. // v1 = v1 & 0xffffffffL;
  154. String hex = Long.toHexString(v2).toUpperCase();
  155.  
  156. return ((type == 0) ? "M" : "G") + "." + v1 + ".A." + StringUtils.leftPad(hex, 3, "0");
  157. }
  158.  
  159. /**
  160. * 將文章編號轉換為檔案名稱
  161. * @param aid 文章編號
  162. * @return
  163. * 轉換後的檔案名稱, 格式將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
  164. * 最後的16進位表示法若未滿3個字將以0從左邊開始補齊<br />
  165. * 範例: M.1451100858.A.71E
  166. */
  167. public static String aidToFileName(String aid){
  168. return aidu2fn( aidc2aidu(aid) );
  169. }
  170.  
  171. /**
  172. * 將文章編號轉換為 WEB 版 URL
  173. * @param boardTitle 文章所屬看板名稱
  174. * @param aid 文章編號
  175. * @return
  176. * WEB 版的完整 URL
  177. */
  178. public static String aidToUrl(String boardTitle, String aid){
  179. if( boardTitle.isEmpty() || aid.isEmpty() ) return "";
  180.  
  181. return DOMAIN_URL + boardTitle + "/" + aidToFileName(aid) + FILE_EXT;
  182. }
  183.  
  184. /**
  185. * 將檔案名稱(也就是 URL 的最後一段 不包含副檔名)轉換為文章編號
  186. * @param fileName 檔案名稱
  187. * @return
  188. * 轉換後的文章編號, 若檔案名稱格式不符則將回傳 null
  189. */
  190. public static String fileNameToAid(String fileName){
  191. return aidu2aidc( fn2aidu(fileName) );
  192. }
  193.  
  194. /**
  195. * 將 URL 轉換為 AID 物件
  196. * @param url PTT WEB 版的 URL
  197. * @return
  198. * 物件內包含 文章編號 與 看板名
  199. * @see AidBean
  200. */
  201. public static AidBean urlToAid(String url){
  202. List<String> urlList = Splitter.on("/").omitEmptyStrings().trimResults().splitToList(url);
  203.  
  204. if( urlList.size() < 4 ) return null;
  205.  
  206. String boardTitle = urlList.get(3);
  207. String fileName = urlList.get(4).replaceAll("\\.(html|htm)", "");
  208. String aid = fileNameToAid( fileName );
  209.  
  210. return AidBean.Builder.anAidBean()
  211. .withBoardTitle( boardTitle )
  212. .withAid( aid )
  213. .build();
  214. }
  215.  
  216. }
  217.  
AidBean
  1. public class AidBean{
  2.  
  3. // 看板名稱
  4. private String boardTitle;
  5.  
  6. // 文章編號
  7. private String aid;
  8.  
  9. public String getBoardTitle() {
  10. return boardTitle;
  11. }
  12.  
  13. public void setBoardTitle(String boardTitle) {
  14. this.boardTitle = boardTitle;
  15. }
  16.  
  17. public String getAid() {
  18. return aid;
  19. }
  20.  
  21. public void setAid(String aid) {
  22. this.aid = aid;
  23. }
  24.  
  25. public static class Builder {
  26. private String boardTitle;
  27. private String aid;
  28.  
  29. private Builder() {
  30. }
  31.  
  32. public static Builder anAidBean() {
  33. return new Builder();
  34. }
  35.  
  36. public Builder withBoardTitle(String boardTitle) {
  37. this.boardTitle = boardTitle;
  38. return this;
  39. }
  40.  
  41. public Builder withAid(String aid) {
  42. this.aid = aid;
  43. return this;
  44. }
  45.  
  46. public Builder but() {
  47. return anAidBean().withBoardTitle(boardTitle).withAid(aid);
  48. }
  49.  
  50. public AidBean build() {
  51. AidBean aidBean = new AidBean();
  52. aidBean.setBoardTitle(boardTitle);
  53. aidBean.setAid(aid);
  54. return aidBean;
  55. }
  56. }
  57. }
各項資料連結
Java - PTT 批踢踢實業坊 Client 軟體開發經驗(1) - 前置準備
PTT 原始碼

No comments:

Post a Comment