code:
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="http://www.montevideo.com.uy/plantillas/temas17/canal-M-17/images/canalm@3.png" group-title="",Canal M
http://wowza.montevideo.com.uy:1936/live/_definst_/mvdstrem/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="http://www.montevideo.com.uy/plantillas/temas17/canal-M-17/images/canalm@3.png" group-title="",Canal M
http://wowza.montevideo.com.uy:1936/live/_definst_/mvdstrem/chunklist_w1488231185.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="http://www.montevideo.com.uy/plantillas/temas17/canal-M-17/images/canalm@3.png" group-title="",Canal M
http://wowza.montevideo.com.uy:1936/live/_definst_/mvdstrem/chunklist_w240300788.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="http://www.montevideo.com.uy/plantillas/temas17/canal-M-17/images/canalm@3.png" group-title="",Canal M
http://wowza.montevideo.com.uy:1936/live/_definst_/mvdstrem/master.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/OxIKeaX.jpg" group-title="Sport",GOLTV
http://trinity-lh.akamaihd.net/i/GolTVModel4A_d@422865/index_500_av-p.m3u8

repo_name: chen/Catiptv


code:
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/iC7DA9n.png" group-title="",Cadena Digital 100.7 FM Caracas (New!)
http://vcp1.myplaytv.com:1935/radiodigitalfm/radiodigitalfm/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/iQtrqmc.jpg" group-title="",Promar TV
http://edge.wms28.lorini.net/promar/promar/live.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/iQtrqmc.jpg" group-title="",Promar TV
http://vcp1.myplaytv.com:1935/promar/promar/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/iQtrqmc.jpg" group-title="",Promar TV
http://vpn58.hdtv.lorini.net/promar/promar/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/Af6z5hL.png" group-title="",TELESUR
http://k3.usastreams.com:1935/telesur/telesur/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/x5Mdzwv.png" group-title="News",TeleSUR English
https://d7g1ebft2592.cloudfront.net/mblivev3/hd/chunklist_w801355776.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/x5Mdzwv.png" group-title="News",TeleSUR English
https://d7g1ebft2592.cloudfront.net/mblivev3/hd/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/x5Mdzwv.png" group-title="News",TeleSUR English
https://d7g1ebft2592.cloudfront.net/mblivev3/hd/playlist.m3u8?fluxustv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/8trJhLq.png" group-title="",TVes
http://5.189.181.203:1935/tves/tves/chunklist_w1468535123.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Spanish" tvg-logo="https://i.imgur.com/nDucnkS.png" group-title="",TVO
http://edge.wms28.lorini.net/tvo/tvo/playlist.m3u8


code:
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="English" tvg-logo="https://i.imgur.com/SlS8xic.png" group-title="",WSVI TV
http://dcunilive30-lh.akamaihd.net/i/dclive_1@534251/master.m3u8


code:
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",AnGiangTV
http://vietcago.net/vstv/angiangtv1.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://tduc.tk/logo/antv.png" group-title="",ANTV
http://antvlive.ab5c6921.cdnviet.com/antv/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://tduc.tk/logo/antv.png" group-title="",ANTV
http://vietcago.net/vstv/antv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://atv.org.vn/lib/images/logo.png" group-title="",ATV
http://221.133.9.35:1935/live/1.stream_720p/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://atv.org.vn/lib/images/atv-tv.png" group-title="",ATV-TH An Giang
http://221.133.9.35:1935/live/1.stream_240p/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",BenTreTV
http://vietcago.net/vstv/bentretv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",BinhPhuocTV1
http://vietcago.net/vstv/binhphuoctv1.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",BinhPhuocTV2
http://vietcago.net/vstv/binhphuoctv2.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://goo.gl/3nWTNy" group-title="",BPTV1 - Binh Phước 1
http://118.69.197.145/hls/bptv1.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://goo.gl/q4pNNB" group-title="",BPTV2 - Binh Phước 2
http://118.69.197.145/hls/bptv2.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://brt.vn/common/v2/images/Banner150219.png" group-title="",BRT
http://113.163.216.23:1935/live/tv2.stream_480p/chunklist_w1716759116.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcTFGIGk4MpV-7eFJWaeVPefzFSx8joT2yJJ76FmIkBntiQBBmov" group-title="",Bến Tre
http://113.163.94.245/hls-live/livepkgr/_definst_/liveevent/thbt.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/ENvLdTj.png" group-title="",Cong Giao TV (VN)
http://saigontvtv.saigon.ns01.us:1935/conggiao/myStream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/gWc8xVD.jpg" group-title="",Cà Mau TV
http://tv.ctvcamau.vn/live/tv/tv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://danangtv.vn/images/drt1.png" group-title="",Da Nang TV1
http://vietcago.net/vstv/danangtv1.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://danangtv.vn/images/drt1.png" group-title="",Da Nang TV1 HD
http://drtdnglive.e49a7c38.cdnviet.com/livedrt1/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/9I4gX5Y.png" group-title="",Da Nang TV2
http://drtdnglive.e49a7c38.cdnviet.com/livestream/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",DakLakTV
http://vietcago.net/vstv/daklaktv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://gardenmedia.vn/kcfinder/upload/images/bang-gia-quang-cao-truyen-hinh-daklak.png" group-title="",DRT
http://118.69.161.10:8134/liveevent.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",GiaLaiTV
http://vietcago.net/vstv/gialaitv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/3OkfjW5.jpg" group-title="",HaTinhTV
http://171.244.141.125:8080/hls/hatinhtv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/BnFWgS7.png" group-title="",HBTV
http://hoabinhtvlive.746b3ddb.cdnviet.com/hoabinhtv/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://hanamtv.vn/template/default/images/logo.png" group-title="",Hà Nam
http://113.160.198.111/Medias/HaNam/Playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/FEffTjz.jpg" group-title="",Hưng Yên
http://live.hungyentv.vn:1935/live/HYTV/chunklist_w769520286.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://uphinhnhanh.com/images/2019/01/31/IMG_20190131_222501.jpg" group-title="",Hưng Yên
http://live.hungyentv.vn:1935/live/HYTV/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://s23.postimg.org/q8pgw4wgb/IBC.jpg" group-title="",IBC TV 57.8
http://liveplay3.malimarcdn.com/ibcnews/ibcnews.sdp/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",KhanhHoaTV
http://vietcago.net/vstv/khanhhoatv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/HqYi1Dw.png" group-title="",KienGiangTV
http://tv.kgtv.vn/live/kgtv/kgtv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/HqYi1Dw.png" group-title="",KienGiangTV
http://vietcago.net/vstv/kiengiangtv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/vzaqu80.png" group-title="",KienGiangTV1
http://tv.kgtv.vn/live/kgtv1/kgtv1.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/vzaqu80.png" group-title="",KienGiangTV1
http://vietcago.net/vstv/kiengiangtv1.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/0vd4Tgb.jpg" group-title="",KonTumTV
http://117.2.88.22/hls-live/livepkgr/_definst_/liveevent/kontumtv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/0vd4Tgb.jpg" group-title="",KonTumTV
http://www.vietcago.net/vstv/kontumtv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://goo.gl/DNboVb" group-title="",LA34 - Long An
http://113.161.229.13/hls-live/livepkgr/_definst_/liveevent/tv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",LongAnTV
http://vietcago.net/vstv/longantv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://i.imgur.com/QnGoFWZ.png" group-title="",Nghệ An
http://113.160.173.47:8080/live/na1_720/index.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://www.htvc.vn/uploads/cache/thumbnail/2015-08-26/8397951-logo-nhan-dan.png" group-title="",NHAN DAN
https://5952e4052a2e8.streamlock.net/live/_definst_/smil:thienviet.smil/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",NhanDanTV
http://vietcago.net/vstv/thnd.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://img.hplus.com.vn/355x200/poster/2018/06/05/606995-Nhandan.png" group-title="",Nhân Dân
https://5952e4052a2e8.streamlock.net/live/smil:thienviet.smil/chunklist_w1106175008_b1544100_sleng.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://img.hplus.com.vn/355x200/poster/2018/06/05/606995-Nhandan.png" group-title="",Nhân dân HD
http://27.118.16.98:1935/live/truyenhinhnhandan720/chunklist_w2006762604.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://img.hplus.com.vn/355x200/poster/2018/06/05/606995-Nhandan.png" group-title="",Nhân dân s2
https://5952e4052a2e8.streamlock.net/live/truyenhinhnhandan720/chunklist_w1220145704.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",PhuYenTV
http://vietcago.net/vstv/phuyentv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://goo.gl/tDSGow" group-title="",PTP - Phú Yên
http://113.161.4.48:8080/phuyen/tv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://uphinhnhanh.com/images/2019/01/31/IMG_20190131_191516.jpg" group-title="",QRT
http://113.161.6.157:8081/hls-live/livepkgr/_definst_/liveevent/livestream.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",QuangNamTV
http://vietcago.net/vstv/quangnamtv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://www.quochoitv.vn/images/logo-qhtv.png" group-title="",Quốc Hội
http://113.164.225.140:1935/live/quochoitvlive.stream_720p/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://s29.postimg.org/x5pf3uayf/SBTN_SET.png" group-title="",SBTN
http://settv.saigon.ns01.us:1935/vietset574tvv/myStream/chunklist_w542335559.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://www.settv574.com/images/file/sqiLlF2i1AgBAjAv/settv574-logo.png" group-title="",SET TV
http://livestream.vnvn.net/hls-live/livepkgr/_definst_/settv574-1/settv574-1.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://s27.postimg.org/65e8ozu8z/set.png" group-title="",SET TV
http://livestream.vnvn.net/hls-live/livepkgr/_definst_/settv574-3/settv574-3.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://uphinhnhanh.com/images/2019/01/31/IMG_20190131_213848.png" group-title="",THGL
http://113.161.25.3:8134/hls/gialaitv/gialaitv.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://hanamtv.vn/template/default/images/logo.png" group-title="",THHN
http://113.160.198.111/Medias/HaNam/480P/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://yt3.ggpht.com/a-/AAuE7mDCM2WqjX5fI2SDnmBk28lqvu1-k1AkWM5-RQ=s900-mo-c-c0xffffffff-rj-k-no" group-title="",THLC
http://42.112.16.101:1935/live_/LCTV/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://tv.vnn.vn/Images/tv/tiengiang%20.png" group-title="",Tien Giang
http://thtg.vn:8001/thtg.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://img.hplus.com.vn/355x200/poster/2018/06/05/330504-TTXVN.png" group-title="",TTXVN
http://live.vnanet.vn/VnewsLive_Android.m3u8
#EXTINF:-1 tvg-id="" tvg-name="TTXVN" tvg-language="Vietnamese" tvg-logo="http://tduc.tk/logo/ttxvn.png" group-title="",TTXVN HD
http://live.vnanet.vn/720P/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="VITV" tvg-language="Vietnamese" tvg-logo="http://tduc.tk/logo/vitv.png" group-title="",VITV
http://210.86.230.202:8134/hls-live/livepkgr/_definst_/liveevent/livestream.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://s30.postimg.org/s0aco3hzl/VIET_MY.png" group-title="",Việt Mỹ TV
http://68.235.37.11:1935/vietmagazine/vietmagazine/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://s24.postimg.org/sf24r4qjp/VNTV.png" group-title="News",Việt News TV
http://vietnews.saigon.ns01.us:1935/VietNews/myStream/chunklist_w107230799.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://s23.postimg.org/f0ie1s30b/VNA.png" group-title="",VNA TV
http://livestream.vnvn.net/hls-live/livepkgr/_definst_/vnatv573-1/vnatv573-1.m3u8?time=1482109571065
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcR8FyAr_hgh7a1qZJwmQbRSKyPO1UwPQiiqR4ljjfRS-QjuqWYk" group-title="",VNA TV
http://livestream.vnvn.net/hls-live/livepkgr/_definst_/vnatv573-3/vnatv573-3.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="http://vgsshop.vn/Data/Sites/1/media/gioi-thieu/logo.png" group-title="",VTVcab11
https://5b0446c43ca13.streamlock.net/dn/htvmuasam.stream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Vietnamese" tvg-logo="" group-title="",YenBaiTV
http://vietcago.net/vstv/yenbaitv.m3u8


code:
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/qYPQaYf.png" group-title="",Rrokum
http://82.114.65.202:1935/live/rrokumtv/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/LTkYnzl.png" group-title="",RTK 1 HD
http://84.22.56.10:1935/rtk1stream/rtk1.stream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/LTkYnzl.png" group-title="",RTK 1 HD
http://stream1.rtkit.com:1935/rtk1stream/rtk1.stream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/RQI1pBn.png" group-title="",RTK 3
http://stream1.rtkit.com:1935/rtk3stream/rtk3.stream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/RQI1pBn.png" group-title="",RTK 3
http://stream2.rtkit.com:1935/rtk3stream/rtk3.stream/chunklist_w2121045972.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/RQI1pBn.png" group-title="",RTK 3
http://stream2.rtkit.com:1935/rtk3stream/rtk3.stream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/H4EWXF3.png" group-title="",RTK 4
http://84.22.56.10:1935/rtk4stream/rtk4.stream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/H4EWXF3.png" group-title="",RTK 4
http://stream1.rtkit.com:1935/rtk4stream/rtk4.stream/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/H4EWXF3.png" group-title="",RTK 4
http://stream2.rtkit.com:1935/rtk4stream/rtk4.stream/chunklist_w599618358.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/KJLm3gi.jpg" group-title="",RTV ZiK
http://uk4.streamingpulse.com:1935/RTVZiK/RTVZiK/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Albanian" tvg-logo="https://i.imgur.com/gfdZq6D.jpg" group-title="",TV Dielli
http://51.15.180.159:8081/play/hls/dielli/index.m3u8


code:
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Arabic" tvg-logo="https://i.imgur.com/poHvbNV.jpg" group-title="",Al Masirah
https://svs.itworkscdn.net/almasiralive/almasira.smil/chunklist_b925000.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Arabic" tvg-logo="https://i.imgur.com/0xHb3XM.jpg" group-title="",Al Masirah Mubacher
https://svs.itworkscdn.net/almasiramubacherlive/almasira.smil/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="Arabic" tvg-logo="https://i.imgur.com/azGNY6n.jpg" group-title="",Yemen TV
https://bozztv.com/36bay2/gin/giniko_yementv_900kb_36bay2/tracks-v1a1/mono.m3u8


code:
#EXTM3U x-tvg-url="http://i.mjh.nz/za/DStv/epg.xml.gz"
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="English" tvg-logo="https://i.imgur.com/mpEcrx8.jpg" group-title="",BOK TV
http://boktv.interworks.in:1935/live/boktv/playlist.m3u8
#EXTINF:-1 tvg-id="8GT" tvg-name="TRACE Africa" tvg-language="English" tvg-logo="https://i.imgur.com/KviG5NU.png" group-title="Music",Trace Africa
http://d2ktlibtvvj8vp.cloudfront.net/trace_africa/6/prog_index.m3u8


code:
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="English" tvg-logo="https://i.imgur.com/5nzTXQM.png" group-title="",Yadah TV
https://streaming.viewsat.eu/viewsatstream18/viewsatstream18.smil/chunklist.m3u8
#EXTINF:-1 tvg-id="" tvg-name="" tvg-language="English" tvg-logo="https://i.imgur.com/GUZYfio.png" group-title="",Yadah TV
https://streaming.viewsat.eu/viewsatstream18/viewsatstream18.smil/chunklist_w1924943123_b2000000.m3u8


code:
#EXTM3U
#EXTINF:-1,Afghanistan
channels/af.m3u
#EXTINF:-1,Albania
channels/al.m3u
#EXTINF:-1,Algeria
channels/dz.m3u
#EXTINF:-1,Andorra
channels/ad.m3u
#EXTINF:-1,Angola
channels/ao.m3u
#EXTINF:-1,Argentina
channels/ar.m3u
#EXTINF:-1,Armenia
channels/am.m3u
#EXTINF:-1,Aruba
channels/aw.m3u
#EXTINF:-1,Australia
channels/au.m3u
#EXTINF:-1,Austria
channels/at.m3u
#EXTINF:-1,Azerbaijan
channels/az.m3u
#EXTINF:-1,Bahamas
channels/bs.m3u
#EXTINF:-1,Bahrain
channels/bh.m3u
#EXTINF:-1,Bangladesh
channels/bd.m3u
#EXTINF:-1,Barbados
channels/bb.m3u
#EXTINF:-1,Belarus
channels/by.m3u
#EXTINF:-1,Belgium
channels/be.m3u
#EXTINF:-1,Bolivia
channels/bo.m3u
#EXTINF:-1,Bosnia and Herzegovina
channels/ba.m3u
#EXTINF:-1,Brazil
channels/br.m3u
#EXTINF:-1,Brunei
channels/bn.m3u
#EXTINF:-1,Bulgaria
channels/bg.m3u
#EXTINF:-1,Burkina Faso
channels/bf.m3u
#EXTINF:-1,Cambodia
channels/kh.m3u
#EXTINF:-1,Cameroon
channels/cm.m3u
#EXTINF:-1,Canada
channels/ca.m3u
#EXTINF:-1,Cape Verde
channels/cv.m3u
#EXTINF:-1,Chile
channels/cl.m3u
#EXTINF:-1,China
channels/cn.m3u
#EXTINF:-1,Colombia
channels/co.m3u
#EXTINF:-1,Costa Rica
channels/cr.m3u
#EXTINF:-1,Croatia
channels/hr.m3u
#EXTINF:-1,Cuba
channels/cu.m3u
#EXTINF:-1,Curaçao
channels/cw.m3u
#EXTINF:-1,Cyprus
channels/cy.m3u
#EXTINF:-1,Czech Republic
channels/cz.m3u
#EXTINF:-1,Democratic Republic of the Congo
channels/cd.m3u
#EXTINF:-1,Denmark
channels/dk.m3u
#EXTINF:-1,Dominican Republic
channels/do.m3u
#EXTINF:-1,Ecuador
channels/ec.m3u
#EXTINF:-1,Egypt
channels/eg.m3u
#EXTINF:-1,El Salvador
channels/sv.m3u
#EXTINF:-1,Equatorial Guinea
channels/gq.m3u
#EXTINF:-1,Estonia
channels/ee.m3u
#EXTINF:-1,Ethiopia
channels/et.m3u
#EXTINF:-1,Faroe Islands
channels/fo.m3u
#EXTINF:-1,Finland
channels/fi.m3u
#EXTINF:-1,Fiji
channels/fj.m3u
#EXTINF:-1,France
channels/fr.m3u
#EXTINF:-1,Gambia
channels/gm.m3u
#EXTINF:-1,Georgia
channels/ge.m3u
#EXTINF:-1,Germany
channels/de.m3u
#EXTINF:-1,Ghana
channels/gh.m3u
#EXTINF:-1,Gibraltar
channels/gi.m3u
#EXTINF:-1,Greece
channels/gr.m3u
#EXTINF:-1,Grenada
channels/gd.m3u
#EXTINF:-1,Guadeloupe
channels/gp.m3u
#EXTINF:-1,Guam
channels/gu.m3u
#EXTINF:-1,Guatemala
channels/gt.m3u
#EXTINF:-1,Guyana
channels/gy.m3u
#EXTINF:-1,Haiti
channels/ht.m3u
#EXTINF:-1,Honduras
channels/hn.m3u
#EXTINF:-1,Hong Kong
channels/hk.m3u
#EXTINF:-1,Hungary
channels/hu.m3u
#EXTINF:-1,Iceland
channels/is.m3u
#EXTINF:-1,India
channels/in.m3u
#EXTINF:-1,Indonesia
channels/id.m3u
#EXTINF:-1,International
channels/int.m3u
#EXTINF:-1,Iran
channels/ir.m3u
#EXTINF:-1,Iraq
channels/iq.m3u
#EXTINF:-1,Ireland
channels/ie.m3u
#EXTINF:-1,Israel
channels/il.m3u
#EXTINF:-1,Italy
channels/it.m3u
#EXTINF:-1,Ivory Coast
channels/ci.m3u
#EXTINF:-1,Jamaica
channels/jm.m3u
#EXTINF:-1,Japan
channels/jp.m3u
#EXTINF:-1,Jordan
channels/jo.m3u
#EXTINF:-1,Kazakhstan
channels/kz.m3u
#EXTINF:-1,Kenya
channels/ke.m3u
#EXTINF:-1,Kosovo
channels/xk.m3u
#EXTINF:-1,Kuwait
channels/kw.m3u
#EXTINF:-1,Kyrgyzstan
channels/kg.m3u
#EXTINF:-1,Laos
channels/la.m3u
#EXTINF:-1,Latvia
channels/lv.m3u
#EXTINF:-1,Lebanon
channels/lb.m3u
#EXTINF:-1,Libya
channels/ly.m3u
#EXTINF:-1,Liechtenstein
channels/li.m3u
#EXTINF:-1,Lithuania
channels/lt.m3u
#EXTINF:-1,Luxembourg
channels/lu.m3u
#EXTINF:-1,Macau
channels/mo.m3u
#EXTINF:-1,Madagascar
channels/mg.m3u
#EXTINF:-1,Malaysia
channels/my.m3u
#EXTINF:-1,Maldives
channels/mv.m3u
#EXTINF:-1,Malta
channels/mt.m3u
#EXTINF:-1,Mexico
channels/mx.m3u
#EXTINF:-1,Moldova
channels/md.m3u
#EXTINF:-1,Monaco
channels/mc.m3u
#EXTINF:-1,Mongolia
channels/mn.m3u
#EXTINF:-1,Montenegro
channels/me.m3u
#EXTINF:-1,Morocco
channels/ma.m3u
#EXTINF:-1,Mozambique
channels/mz.m3u
#EXTINF:-1,Myanmar
channels/mm.m3u
#EXTINF:-1,Nepal
channels/np.m3u
#EXTINF:-1,Netherlands
channels/nl.m3u
#EXTINF:-1,New Zealand
channels/nz.m3u
#EXTINF:-1,Nicaragua
channels/ni.m3u
#EXTINF:-1,Niger
channels/ne.m3u
#EXTINF:-1,Nigeria
channels/ng.m3u
#EXTINF:-1,North Korea
channels/kp.m3u
#EXTINF:-1,North Macedonia
channels/mk.m3u
#EXTINF:-1,Norway
channels/no.m3u
#EXTINF:-1,Oman
channels/om.m3u
#EXTINF:-1,Pakistan
channels/pk.m3u
#EXTINF:-1,Palestine
channels/ps.m3u
#EXTINF:-1,Panama
channels/pa.m3u
#EXTINF:-1,Paraguay
channels/py.m3u
#EXTINF:-1,Peru
channels/pe.m3u
#EXTINF:-1,Philippines
channels/ph.m3u
#EXTINF:-1,Poland
channels/pl.m3u
#EXTINF:-1,Portugal
channels/pt.m3u
#EXTINF:-1,Puerto Rico
channels/pr.m3u
#EXTINF:-1,Qatar
channels/qa.m3u
#EXTINF:-1,Republic of the Congo
channels/cg.m3u
#EXTINF:-1,Romania
channels/ro.m3u
#EXTINF:-1,Russia
channels/ru.m3u
#EXTINF:-1,Rwanda
channels/rw.m3u
#EXTINF:-1,Saint Kitts and Nevis
channels/kn.m3u
#EXTINF:-1,San Marino
channels/sm.m3u
#EXTINF:-1,Saudi Arabia
channels/sa.m3u
#EXTINF:-1,Senegal
channels/sn.m3u
#EXTINF:-1,Serbia
channels/rs.m3u
#EXTINF:-1,Sierra Leone
channels/sl.m3u
#EXTINF:-1,Singapore
channels/sg.m3u
#EXTINF:-1,Sint Maarten
channels/sx.m3u
#EXTINF:-1,Slovakia
channels/sk.m3u
#EXTINF:-1,Slovenia
channels/si.m3u
#EXTINF:-1,Somalia
channels/so.m3u
#EXTINF:-1,South Africa
channels/za.m3u
#EXTINF:-1,South Korea
channels/kr.m3u
#EXTINF:-1,Spain
channels/es.m3u
#EXTINF:-1,Sri Lanka
channels/lk.m3u
#EXTINF:-1,Sudan
channels/sd.m3u
#EXTINF:-1,Suriname
channels/sr.m3u
#EXTINF:-1,Sweden
channels/se.m3u
#EXTINF:-1,Switzerland
channels/ch.m3u
#EXTINF:-1,Syria
channels/sy.m3u
#EXTINF:-1,Taiwan
channels/tw.m3u
#EXTINF:-1,Tajikistan
channels/tj.m3u
#EXTINF:-1,Tanzania
channels/tz.m3u
#EXTINF:-1,Thailand
channels/th.m3u
#EXTINF:-1,Togo
channels/tg.m3u
#EXTINF:-1,Trinidad and Tobago
channels/tt.m3u
#EXTINF:-1,Tunisia
channels/tn.m3u
#EXTINF:-1,Turkey
channels/tr.m3u
#EXTINF:-1,Turkmenistan
channels/tm.m3u
#EXTINF:-1,Uganda
channels/ug.m3u
#EXTINF:-1,Ukraine
channels/ua.m3u
#EXTINF:-1,United Arab Emirates
channels/ae.m3u
#EXTINF:-1,United Kingdom
channels/uk.m3u
#EXTINF:-1,United States
channels/us.m3u
#EXTINF:-1,Uruguay
channels/uy.m3u
#EXTINF:-1,Uzbekistan
channels/uz.m3u
#EXTINF:-1,Venezuela
channels/ve.m3u
#EXTINF:-1,Vietnam
channels/vn.m3u
#EXTINF:-1,Virgin Islands of the United States
channels/vi.m3u
#EXTINF:-1,Western Sahara
channels/eh.m3u
#EXTINF:-1,Yemen
channels/ye.m3u
#EXTINF:-1,Zimbabwe
channels/zw.m3u


iptv

test: node scripts/test.js

format: node scripts/format.js

generate: node scripts/generate.js

update-readme: node scripts/update-readme.js

author: Arhey <free.arhey@gmail.com>

license: MIT


code:
const helper = require('./helper')

const config = {
  debug: process.env.npm_config_debug || false,
  country: process.env.npm_config_country,
  exclude: process.env.npm_config_exclude,
  epg: process.env.npm_config_epg || false
}

let updated = 0

async function main() {
  console.log(`Parsing index...`)
  const index = parseIndex()

  for(let item of index.items) {
    console.log(`Processing '${item.url}'...`)
    let playlist = parsePlaylist(item.url)
    if(config.debug) { console.log(`Sorting channels...`) }
    playlist = sortChannels(playlist)
    if(config.debug) { console.log(`Removing duplicates...`) }
    playlist = removeDuplicates(playlist)

    if(config.epg) {
      const tvgUrl = playlist.header.attrs['x-tvg-url']
      if(tvgUrl) {
        if(config.debug) { console.log(`Loading EPG from '${tvgUrl}'...`) }
        const epg = await loadEPG(tvgUrl)
        if(config.debug) { console.log(`Adding the missing data from EPG...`) }
        playlist = addDataFromEPG(playlist, epg)
      } else {
        if(config.debug) { console.log(`EPG source is not found`) }
      }
    }

    if(playlist.changed) {
      updatePlaylist(item.url, playlist)
      updated++
    } else {
      console.log('Nothing is changed')
    }
  }

  console.log(`Updated ${updated} playlist(s)`)

  console.log('Done.\n')
}

function parseIndex() {
  const playlist = helper.parsePlaylist('index.m3u')
  playlist.items = helper.filterPlaylists(playlist.items, config.country, config.exclude)

  console.log(`Found ${playlist.items.length} playlist(s)`)

  return playlist
}

function parsePlaylist(url) {
  const playlist = helper.parsePlaylist(url)

  playlist.items = playlist.items.map(item => {
    return helper.createChannel(item)
  })

  return playlist
}

function sortChannels(playlist) {
  const channels = JSON.stringify(playlist.items)
  playlist.items = helper.sortBy(playlist.items, ['title', 'url'])
  if(channels !== JSON.stringify(playlist.items)) { playlist.changed = true }

  return playlist
}

function removeDuplicates(playlist) {
  let buffer = {}
  const channels = JSON.stringify(playlist.items)
  playlist.items = playlist.items.filter(i => {
    let result = typeof buffer[i.url] === 'undefined'

    if(result) {
      buffer[i.url] = true
    } else {
      if(config.debug) { console.log(`Duplicate of '${i.title}' has been removed`) }
    }

    return result
  })

  if(channels !== JSON.stringify(playlist.items)) { playlist.changed = true }

  return playlist
}

async function loadEPG(url) {
  try {
    return await helper.parseEPG(url)
  } catch(err) {
    console.error(`Error: could not load '${url}'`)
    return
  }
}

function addDataFromEPG(playlist, epg) {
  if(!epg) return playlist

  for(let item of playlist.items) {
    if(!item.id) continue

    const channel = epg.channels[item.id]

    if(!channel) continue

    if(!item.name && channel.name.length) {
      item.name = channel.name[0].value
      playlist.changed = true
      if(config.debug) { console.log(`Added tvg-name '${item.name}' to '${item.title}'`) }
    }

    if(!item.language && channel.name.length && channel.name[0].lang) {
      item.language = channel.name[0].lang
      playlist.changed = true
      if(config.debug) { console.log(`Added tvg-language '${item.language}' to '${item.title}'`) }
    }

    if(!item.logo && channel.icon.length) {
      item.logo = channel.icon[0]
      playlist.changed = true
      if(config.debug) { console.log(`Added tvg-logo '${item.logo}' to '${item.title}'`) }
    }
  }

  return playlist
}

function updatePlaylist(filepath, playlist) {
  helper.createFile(filepath, playlist.getHeader())
  for(let channel of playlist.items) {
    helper.appendToFile(filepath, channel.toShortString())
  }

  console.log(`Playlist '${filepath}' has been updated`)
}

main()


code:
const helper = require('./helper')

const ROOT_DIR = './.gh-pages'

let list = {
  all: [],
  countries: {},
  languages: {},
  categories: {}
}

function main() {
  console.log(`Parsing index...`)
  parseIndex()
  console.log('Creating root directory...')
  createRootDirectory()
  console.log('Creating .nojekyll...')
  createNoJekyllFile()
  console.log('Generating index.m3u...')
  generateIndex()
  console.log('Generating index.country.m3u...')
  generateCountryIndex()
  console.log('Generating index.language.m3u...')
  generateLanguageIndex()
  console.log('Generating index.category.m3u...')
  generateCategoryIndex()
  console.log('Generating /countries...')
  generateCountries()
  console.log('Generating /categories...')
  generateCategories()
  console.log('Generating /languages...')
  generateLanguages()
  console.log('Done.\n')

  console.log(`Countries: ${Object.values(list.countries).length}. Languages: ${Object.values(list.languages).length}. Categories: ${Object.values(list.categories).length}. Channels: ${list.all.length}.`)
}

function createRootDirectory() {
  helper.createDir(ROOT_DIR)
}

function createNoJekyllFile() {
  helper.createFile(`${ROOT_DIR}/.nojekyll`)
}

function parseIndex() {
  const root = helper.parsePlaylist('index.m3u')

  let countries = {}
  let languages = {}
  let categories = {}

  for(let rootItem of root.items) {
    const playlist = helper.parsePlaylist(rootItem.url)
    const countryCode = helper.getBasename(rootItem.url).toLowerCase()
    const countryName = rootItem.name

    for(let item of playlist.items) {
      const channel = helper.createChannel(item)
      channel.countryCode = countryCode
      channel.countryName = countryName
      channel.epg = playlist.header.attrs['x-tvg-url'] || ''

      // all
      list.all.push(channel)

      // country
      if(!countries[countryCode]) {
        countries[countryCode] = []
      }
      countries[countryCode].push(channel)

      // language
      const languageCode = helper.getISO6391Code(channel.language) || 'undefined'
      if(!languages[languageCode]) {
        languages[languageCode] = []
      }
      languages[languageCode].push(channel)

      // category
      const categoryCode = channel.group.toLowerCase() || 'other'
      if(!categories[categoryCode]) {
        categories[categoryCode] = []
      }
      categories[categoryCode].push(channel)
    }
  }

  list.countries = countries
  list.languages = languages
  list.categories = categories
}

function generateIndex() {
  const filename = `${ROOT_DIR}/index.m3u`
  helper.createFile(filename, '#EXTM3U\n')

  const channels = helper.sortBy(list.all, ['title', 'url'])
  for(let channel of channels) {
    helper.appendToFile(filename, channel.toString())
  }
}

function generateCountryIndex() {
  const filename = `${ROOT_DIR}/index.country.m3u`
  helper.createFile(filename, '#EXTM3U\n')

  const channels = helper.sortBy(list.all, ['countryName', 'title', 'url'])
  for(let channel of channels) {
    const group = channel.group
    channel.group = channel.countryName
    helper.appendToFile(filename, channel.toString())
    channel.group = group
  }
}

function generateLanguageIndex() {
  const filename = `${ROOT_DIR}/index.language.m3u`
  helper.createFile(filename, '#EXTM3U\n')

  const channels = helper.sortBy(list.all, ['language', 'title', 'url'])
  for(let channel of channels) {
    const group = channel.group
    channel.group = channel.language
    helper.appendToFile(filename, channel.toString())
    channel.group = group
  }
}

function generateCategoryIndex() {
  const filename = `${ROOT_DIR}/index.category.m3u`
  helper.createFile(filename, '#EXTM3U\n')

  const channels = helper.sortBy(list.all, ['group', 'title', 'url'])
  for(let channel of channels) {
    helper.appendToFile(filename, channel.toString())
  }
}

function generateCountries() {
  const outputDir = `${ROOT_DIR}/countries`
  helper.createDir(outputDir)

  for(let cid in list.countries) {
    let country = list.countries[cid]
    const filename = `${outputDir}/${cid}.m3u`
    helper.createFile(filename, '#EXTM3U\n')

    const channels = helper.sortBy(Object.values(country), ['title', 'url'])
    for(let channel of channels) {
      helper.appendToFile(filename, channel.toString())
    }
  }
}

function generateCategories() {
  const outputDir = `${ROOT_DIR}/categories`
  helper.createDir(outputDir)

  for(let cid in list.categories) {
    let category = list.categories[cid]
    const filename = `${outputDir}/${cid}.m3u`
    helper.createFile(filename, '#EXTM3U\n')

    const channels = helper.sortBy(Object.values(category), ['title', 'url'])
    for(let channel of channels) {
      helper.appendToFile(filename, channel.toString())
    }
  }
}

function generateLanguages() {
  const outputDir = `${ROOT_DIR}/languages`
  helper.createDir(outputDir)

  for(let lid in list.languages) {
    let language = list.languages[lid]
    const filename = `${outputDir}/${lid}.m3u`
    helper.createFile(filename, '#EXTM3U\n')

    const channels = helper.sortBy(Object.values(language), ['title', 'url'])
    for(let channel of channels) {
      helper.appendToFile(filename, channel.toString())
    }
  }
}

main()


code:
const fs = require("fs")
const path = require('path')
const playlistParser = require('iptv-playlist-parser')
const axios = require('axios')
const zlib = require("zlib")
const epgParser = require('epg-parser')
const urlParser = require('url')
const ISO6391 = require('iso-639-1')
const escapeStringRegexp = require('escape-string-regexp')
const markdownInclude = require('markdown-include')

let cache = {}
let helper = {}

helper.sortBy = function(arr, fields) {
  return arr.sort((a, b) => {
    for(let field of fields) {
      if(a[field].toLowerCase()  b[field].toLowerCase()) { return 1 }
    }
    return 0
  })
}

helper.createDir = function(dir) {
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir)
  }
}

helper.compileMarkdown = function(filepath) {
  return markdownInclude.compileFiles(path.resolve(__dirname, filepath))
}

helper.escapeStringRegexp = function(scring) {
  return escapeStringRegexp(string)
}

helper.getISO6391Name = function(code) {
  return ISO6391.getName(code)
}

helper.getISO6391Code = function(name) {
  return ISO6391.getCode(name)
}

helper.parsePlaylist = function(filename) {
  const content = this.readFile(filename)
  const result = playlistParser.parse(content)

  return new Playlist(result)
}

helper.parseEPG = async function(url) {
  const content = await this.getEPG(url)
  const result = epgParser.parse(content)
  const channels = {}
  for(let channel of result.channels) {
    channels[channel.id] = channel
  }

  return Promise.resolve({
    url,
    channels
  })
}

helper.getEPG = function(url) {
  return new Promise((resolve, reject) => {
    var buffer = []
    axios({
      method: 'get',
      url: url,
      responseType: 'stream',
      timeout: 60000
    }).then(res => {
      let stream
      if(/\.gz$/i.test(url)) {
        let gunzip = zlib.createGunzip()
        res.data.pipe(gunzip)
        stream = gunzip
      } else {
        stream = res.data
      }

      stream.on('data', function(data) {
        buffer.push(data.toString())
      }).on("end", function() {
        resolve(buffer.join(""))
      }).on("error", function(e) {
        reject(e)
      })
    }).catch(e => {
      reject(e)
    })
  })
}

helper.readFile = function(filename) {
  return fs.readFileSync(path.resolve(__dirname) + `/../${filename}`, { encoding: "utf8" })
}

helper.appendToFile = function(filename, data) {
  fs.appendFileSync(path.resolve(__dirname) + '/../' + filename, data)
}

helper.createFile = function(filename, data = '') {
  fs.writeFileSync(path.resolve(__dirname) + '/../' + filename, data)
}

helper.getBasename = function(filename) {
  return path.basename(filename, path.extname(filename))
}

helper.getUrlPath = function(u) {
  let parsed = urlParser.parse(u)
  let searchQuery = parsed.search || ''
  let path = parsed.host + parsed.pathname + searchQuery

  return path.toLowerCase()
}

helper.filterPlaylists = function(arr, include = '', exclude = '') {
  if(include) {
    const included = include.split(',').map(filename => `channels/${filename}.m3u`)

    return arr.filter(i => included.indexOf(i.url) > -1)
  }

  if(exclude) {
    const excluded = exclude.split(',').map(filename => `channels/${filename}.m3u`)

    return arr.filter(i => excluded.indexOf(i.url) === -1)
  }

  return arr
}

helper.generateTable = function(data, options) {
  let output = '
\n'

  output += '\t
\n\t\t
'
  for (let column of options.columns) {
    output += `<th align="${column.align}">${column.name}</th>`
  }
  output += '
\n\t
\n'

  output += '\t
\n'
  for (let item of data) {
    output += '\t\t
'
    let i = 0
    for (let prop in item) {
      const column = options.columns[i]
      let nowrap = column.nowrap
      let align = column.align
      output += `<td align="${align}"${nowrap ? ' nowrap' : ''}>${item[prop]}</td>`
      i++
    }
    output += '
\n'
  }
  output += '\t
\n'

  output += '
'

  return output
}

helper.createChannel = function(data) {
  return new Channel({
    id: data.tvg.id,
    name: data.tvg.name,
    language: data.tvg.language,
    logo: data.tvg.logo,
    group: data.group.title,
    url: data.url,
    title: data.name
  })
}

helper.writeToLog = function(country, msg, url) {
  var now = new Date()
  var line = `${country}: ${msg} '${url}'`
  this.appendToFile('error.log', now.toISOString() + ' ' + line + '\n')
}

helper.parseMessage = function(err, u) {
  if(!err || !err.message) return

  const msgArr = err.message.split('\n')

  if(msgArr.length === 0) return

  const line = msgArr.find(line => {
    return line.indexOf(u) === 0
  })

  if(!line) return

  return line.replace(`${u}: `, '')
}

class Playlist {
  constructor(data) {
    this.header = data.header
    this.items = data.items
    this.changed = false
  }

  getHeader() {
    let parts = ['#EXTM3U']
    for(let key in this.header.attrs) {
      let value = this.header.attrs[key]
      if(value) {
        parts.push(`${key}="${value}"`)
      }
    }

    return `${parts.join(' ')}\n`
  }
}

class Channel {
  constructor(data) {
    this.id = data.id
    this.name = data.name
    this.language = this._filterLanguage(data.language)
    this.logo = data.logo
    this.group = this._filterGroup(data.group)
    this.url = data.url
    this.title = data.title
  }

  _filterGroup(groupTitle) {
    if(!groupTitle) return ''

    const supportedCategories = [ 'Auto','Business', 'Classic','Comedy','Documentary','Education','Entertainment', 'Family','Fashion','Food', 'General', 'Health', 'History', 'Hobby', 'Kids', 'Legislative','Lifestyle','Local', 'Movies', 'Music', 'News', 'Quiz', 'Religious','Sci-Fi', 'Shop', 'Sport', 'Travel', 'Weather', 'XXX' ]
    const groupIndex = supportedCategories.map(g => g.toLowerCase()).indexOf(groupTitle.toLowerCase())

    if(groupIndex === -1) {
      groupTitle = ''
    } else {
      groupTitle = supportedCategories[groupIndex]
    }

    return groupTitle
  }

  _filterLanguage(languageName) {
    if(ISO6391.getCode(languageName) !== '') {
      return languageName
    }

    return ''
  }

  toString() {
    const country = this.countryCode.toUpperCase()
    const epg = (this.id && this.epg) ? this.epg : ''
    const info = `-1 tvg-id="${this.id}" tvg-name="${this.name}" tvg-language="${this.language}" tvg-logo="${this.logo}" tvg-country="${country}" tvg-url="${epg}" group-title="${this.group}",${this.title}`

    return '#EXTINF:' + info + '\n' + this.url + '\n'
  }

  toShortString() {
    const info = `-1 tvg-id="${this.id}" tvg-name="${this.name}" tvg-language="${this.language}" tvg-logo="${this.logo}" group-title="${this.group}",${this.title}`

    return '#EXTINF:' + info + '\n' + this.url + '\n'
  }
}

module.exports = helper


code:
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0

const helper = require('./helper')
const ffmpeg = require('fluent-ffmpeg')

const config = {
  debug: process.env.npm_config_debug || false,
  country: process.env.npm_config_country,
  exclude: process.env.npm_config_exclude,
  timeout: 10
}

let stats = {
  playlists: 0,
  channels: 0,
  failures: 0
}

async function test() {

  const playlist = helper.parsePlaylist('index.m3u')

  const countries = helper.filterPlaylists(playlist.items, config.country, config.exclude)

  for(let country of countries) {

    stats.playlists++

    console.log(`Processing '${country.url}'...`)

    const playlist = helper.parsePlaylist(country.url)

    for(let item of playlist.items) {

      stats.channels++

      if(config.debug) { console.log(`Checking '${item.url}'...`) }

      await new Promise(resolve => {

        const timeout = setTimeout(() => {

          resolve()

        }, config.timeout * 1000)

        ffmpeg(item.url, { timeout: 60 }).ffprobe((err) => {

          if(err) {
            const message = helper.parseMessage(err, item.url)

            stats.failures++

            helper.writeToLog(country.url, message, item.url)

            console.log(`${message} '${item.url}'`)
          }

          clearTimeout(timeout)

          resolve()

        })
      })

    }
  }

  if(stats.failures === 0) {

    console.log(`OK (${stats.playlists} playlists, ${stats.channels} channels)`)

  } else {

    console.log(`FAILURES! (${stats.playlists} playlists, ${stats.channels} channels, ${stats.failures} failures)`)

    process.exit(1)

  }

}

console.log('Test is running...')

test()


code:
const helper = require('./helper')

let output = {
  countries: [],
  languages: [],
  categories: []
}

function main() {
  console.log(`Parsing index...`)
  parseIndex()
  console.log(`Generating countries table...`)
  generateCountriesTable()
  console.log(`Generating languages table...`)
  generateLanguagesTable()
  console.log(`Generating categories table...`)
  generateCategoriesTable()
  console.log(`Generating README.md...`)
  generateReadme()
  console.log(`Done.`)
}

function parseIndex() {
  const root = helper.parsePlaylist('index.m3u')

  let countries = {}
  let languages = {}
  let categories = {}
  for(let rootItem of root.items) {
    const playlist = helper.parsePlaylist(rootItem.url)
    const countryName = rootItem.name
    const countryCode = helper.getBasename(rootItem.url).toLowerCase()
    const countryEpg = playlist.header.attrs['x-tvg-url'] ? `<code>${playlist.header.attrs['x-tvg-url']}</code>` : ''

    for(let item of playlist.items) {

      // countries
      if(countries[countryCode]) {
        countries[countryCode].channels++
      } else {
        countries[countryCode] = {
          country: countryName,
          channels: 1,
          playlist: `<code>https://iptv-org.github.io/iptv/countries/${countryCode}.m3u</code>`,
          epg: countryEpg
        }
      }

      // languages
      const languageName = item.tvg.language || 'Undefined'
      const languageCode = helper.getISO6391Code(languageName) || 'undefined'
      if(languages[languageCode]) {
        languages[languageCode].channels++
      } else {
        languages[languageCode] = {
          language: languageName,
          channels: 1,
          playlist: `<code>https://iptv-org.github.io/iptv/languages/${languageCode}.m3u</code>`
        }
      }

      // categories
      const categoryName = item.group.title || 'Other'
      const categoryCode = categoryName.toLowerCase()
      if(categories[categoryCode]) {
        categories[categoryCode].channels++
      } else {
        categories[categoryCode] = {
          category: categoryName,
          channels: 1,
          playlist: `<code>https://iptv-org.github.io/iptv/categories/${categoryCode}.m3u</code>`
        }
      }
    }
  }

  output.countries = Object.values(countries)
  output.languages = Object.values(languages)
  output.categories = Object.values(categories)
}

function generateCountriesTable() {
  const table = helper.generateTable(output.countries, {
    columns: [
      { name: 'Country', align: 'left' },
      { name: 'Channels', align: 'right' },
      { name: 'Playlist', align: 'left', nowrap: true },
      { name: 'EPG', align: 'left' }
    ]
  })

  helper.createFile('./.readme/_countries.md', table)
}

function generateLanguagesTable() {
  output.languages.sort((a, b) => {
    if(a.language === 'Undefined') { return 1 }
    if(b.language === 'Undefined') { return -1 }
    if(a.language < b.language) { return -1 }
    if(a.language > b.language) { return 1 }
    return 0
  })

  const table = helper.generateTable(output.languages, {
    columns: [
      { name: 'Language', align: 'left' },
      { name: 'Channels', align: 'right' },
      { name: 'Playlist', align: 'left' }
    ]
  })

  helper.createFile('./.readme/_languages.md', table)
}

function generateCategoriesTable() {
  output.categories.sort((a, b) => {
    if(a.category === 'Other') { return 1 }
    if(b.category === 'Other') { return -1 }
    if(a.category < b.category) { return -1 }
    if(a.category > b.category) { return 1 }
    return 0
  })

  const table = helper.generateTable(output.categories, {
    columns: [
      { name: 'Category', align: 'left' },
      { name: 'Channels', align: 'right' },
      { name: 'Playlist', align: 'left' }
    ]
  })

  helper.createFile('./.readme/_categories.md', table)
}

function generateReadme() {
  helper.compileMarkdown('../.readme/config.json')
}

main()


code:
text-rendering
tags
src/**/*.o

path: .gitignore

license: mit


code:
LIBS = -lGL -lglfw -ldl -lm -lfreetype
INCS = -Iexternal/glad/include -I/usr/include/freetype2
CFLAGS = -std=c99 -O2 -march=znver1 -Wall -Wno-char-subscripts

SRC = src/main.c src/file_ops.c src/text_rendering.c external/glad/src/glad.c
OBJ = $(SRC:.c=.o)

text-rendering: $(OBJ)
	$(CC) $(OBJ) $(LIBS) -o $@

.c.o:
	$(CC) $(CFLAGS) $(INCS) -c $< -o $@

src/text_rendering.o: src/text_rendering.h
src/file_ops.o: src/file_ops.h

gentags:
	ctags `find src -name "*.c" -or -name "*.h"`

clean:
	rm -f text-rendering $(OBJ) tags

path: Makefile


code:
# text-rendering
An example program written in C that loads a text font and shows it on the
screen with openGL


code:
#version 460 core
out vec4 FragColor;

in vec2 TexCoord;

// texture sampler
uniform sampler2D texture1;
uniform vec3 textColor;

void main()
{
	FragColor = vec4(textColor, texture(texture1, TexCoord).r);
}


code:
#version 460 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 projection;

void main()
{
	gl_Position = projection * vec4(aPos, 0, 1);
	TexCoord = aTexCoord;
}


code:
#include <stdio.h>
#include <stdlib.h>

char *
file_to_str(const char *file_path)
{
	FILE *fp;
	unsigned int length;
	char *buffer;

	fp = fopen(file_path, "rb");
	if (fp) {
		fseek(fp, 0, SEEK_END);
		length = ftell(fp);
		buffer = malloc(length + 1);
		fseek(fp, 0, SEEK_SET);
		if (fread(buffer, 1, length, fp) != length)
			exit(1);
		fclose(fp);

		buffer[length] = '\0';

		return buffer;
	} else return 0;
}

void
file_to_str_cleanup(char *buffer)
{
	free(buffer);
}


code:
#ifndef FILE_OPS_H
#define FILE_OPS_H

char *file_to_str(const char *file_path);
void file_to_str_cleanup(char *buffer);

#endif


code:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <cglm/cglm.h>

#include "file_ops.h"
#include "text_rendering.h"

struct screen {
	short size[2];
	bool perspective_changed;
} main_screen = {{800, 800}, true};

void
framebuffer_size_callback(GLFWwindow* window, int w, int h)
{
	glViewport(0, 0, w, h);

	main_screen.perspective_changed = true;

	main_screen.size[0] = w;
	main_screen.size[1] = h;
}

int
main(void)
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(main_screen.size[0],
			main_screen.size[1], "text-rendering", 0, 0);
	glfwMakeContextCurrent(window);

	gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);

	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	struct text_rendering main_text;

	char *vertex_source = file_to_str("shaders/vertex.glsl");
	char *fragment_source = file_to_str("shaders/fragment.glsl");
	text_rendering_init(
		vertex_source, fragment_source,
		"/usr/share/fonts/liberation-fonts/LiberationSans-Regular.ttf",
		&main_text
	);

	file_to_str_cleanup(vertex_source);
	file_to_str_cleanup(fragment_source);

	while(!glfwWindowShouldClose(window)) {
		if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
			glfwSetWindowShouldClose(window, 1);

		glClearColor(.2, .3, .3, 1);
		glClear(GL_COLOR_BUFFER_BIT);

		if (main_screen.perspective_changed) {
			main_screen.perspective_changed = false;

			text_rendering_perspective(
				(vec2){main_screen.size[0],
					main_screen.size[1]},
				&main_text
			);
		}

		text_rendering_render("This is sample text", (vec2){25, 25}, 1,
				(vec3){.5, .8, .2}, &main_text);

		text_rendering_render("Mean3Voice-amid!!", (vec2){500, 700},
				.75, (vec3){.8, .2, .5}, &main_text);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	text_rendering_cleanup(&main_text);
	glfwTerminate();
}


code:
#include <glad/glad.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "text_rendering.h"

void
text_rendering_init(const char *vertex_shader_source,
		const char *fragment_shader_source,
		const char *font, struct text_rendering *dest)
{
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glGenVertexArrays(1, &(dest->VAO));
	glGenBuffers(1, &(dest->VBO));

	glBindVertexArray(dest->VAO);
	glBindBuffer(GL_ARRAY_BUFFER, dest->VBO);
	// Allocate for a quad with 2 position and 2 texture coords
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * 4, NULL,
			GL_DYNAMIC_DRAW);

	// position
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
	glEnableVertexAttribArray(0);
	// texture coord
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
			(void*)(2 * sizeof(float)));
	glEnableVertexAttribArray(1);

	glBindVertexArray(0);

	FT_Library ft;
	FT_Init_FreeType(&ft);

	FT_Face face;
	FT_New_Face(ft, font, 0, &face);
	FT_Set_Pixel_Sizes(face, 0, 48);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	unsigned int texture;
	for (unsigned char c = 0; c < 128; ++c) {
		if (FT_Load_Char(face, c, FT_LOAD_RENDER))
			continue;

		glGenTextures(1, &texture);
		glBindTexture(GL_TEXTURE_2D, texture);
		// set the texture wrapping parameters
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
				GL_CLAMP_TO_BORDER);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
				GL_CLAMP_TO_BORDER);
		// set texture filtering parameters
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
				GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
				GL_LINEAR);

		glTexImage2D(
			GL_TEXTURE_2D,
			0,
			GL_RED,
			face->glyph->bitmap.width,
			face->glyph->bitmap.rows,
			0,
			GL_RED,
			GL_UNSIGNED_BYTE,
			face->glyph->bitmap.buffer
		);

		dest->characters[c].texture_id = texture;
		dest->characters[c].size[0] = face->glyph->bitmap.width;
		dest->characters[c].size[1] = face->glyph->bitmap.rows;
		dest->characters[c].bearing[0] = face->glyph->bitmap_left;
		dest->characters[c].bearing[1] = face->glyph->bitmap_top;
		dest->characters[c].advance = face->glyph->advance.x >> 6;
	}
	glBindTexture(GL_TEXTURE_2D, 0);
	FT_Done_Face(face);
	FT_Done_FreeType(ft);

	unsigned int vertex_shader, fragment_shader;

	vertex_shader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
	glCompileShader(vertex_shader);

	fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
	glCompileShader(fragment_shader);

	dest->shader_program = glCreateProgram();
	glAttachShader(dest->shader_program, vertex_shader);
	glAttachShader(dest->shader_program, fragment_shader);
	glLinkProgram(dest->shader_program);

	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);

	dest->projection_loc = glGetUniformLocation(dest->shader_program,
			"projection");
	dest->textcolor_loc = glGetUniformLocation(dest->shader_program,
			"textColor");
}

void
text_rendering_perspective(vec2 size, struct text_rendering *text)
{
	glUseProgram(text->shader_program);

	mat4 projection;

	glm_ortho(0, size[0], 0, size[1], 0, 1, projection);
	glUniformMatrix4fv(text->projection_loc, 1, GL_FALSE,
			(float *)projection);

	glUseProgram(0);
}

void
text_rendering_render(const char *string, vec2 pos, float scale, vec3 color,
		struct text_rendering *text)
{
	glUseProgram(text->shader_program);
	glBindVertexArray(text->VAO);

	glUniform3f(text->textcolor_loc, color[0], color[1], color[2]);
	while (*string) {
		struct ft_character ch = text->characters[*string++];

		float xpos = pos[0] + ch.bearing[0] * scale;
		float ypos = pos[1] - (ch.size[1] - ch.bearing[1]) * scale;
		float w = ch.size[0] * scale;
		float h = ch.size[1] * scale;

		float vertices[4][4] = {
			{ xpos + w, ypos + h,  1, 0 },
			{ xpos + w, ypos,      1, 1 },
			{ xpos,     ypos + h,  0, 0 },
			{ xpos,     ypos,      0, 1 },
		};

		glBindTexture(GL_TEXTURE_2D, ch.texture_id);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

		pos[0] += ch.advance * scale;
	}

	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glUseProgram(0);
}

void
text_rendering_cleanup(struct text_rendering *text)
{
	glDeleteVertexArrays(1, &(text->VAO));
	glDeleteBuffers(1, &(text->VBO));
	glDeleteProgram(text->shader_program);
}


code:
#ifndef TEXT_RENDERING_H
#define TEXT_RENDERING_H

#include <cglm/cglm.h>

struct ft_character {
	unsigned int texture_id;
	short size[2];
	short bearing[2];
	short advance;
};

struct text_rendering {
	unsigned int VAO;
	unsigned int VBO;
	unsigned int shader_program;
	int projection_loc;
	int textcolor_loc;
	struct ft_character characters[128];
};

void text_rendering_init(const char *vertex_shader_source,
		const char *fragment_shader_source,
		const char *font, struct text_rendering *dest);

void text_rendering_perspective(vec2 size, struct text_rendering *text);

void text_rendering_render(const char *string, vec2 pos, float scale,
		vec3 color, struct text_rendering *text);

void text_rendering_cleanup(struct text_rendering *text);

#endif


code:
/git_clones
/hg_clones
/gclient
/out
/release
/alpha
/nightly
/testbuild
/rbm.local.conf
/logs
/tmp

repo_name: JeremyRand/tor


code:
  Explanation of variables

- `$(BUILD_SERVER)` : the server the main builder is using to build a tor-browser release
- `$(STAGING_SERVER)` : the server the signer is using to to run the signing process
- `$(TOR_LAUNCHER_VERSION)` : version of `tor-launcher`, used in tags
    - example : `0.2.33`
- `$(ESR_VERSION)` : the Mozilla defined ESR version, used in various places for building tor-browser tags, labels, etc
    - example : `91.6.0`
- `$(ESR_TAG)` : the Mozilla defined hg (Mercurial) tag associated with `$(ESR_VERSION)`
    - exmaple : `FIREFOX_91_7_0esr_BUILD2`
- `$(ESR_TAG_PREV)` : the Mozilla defined hg (Mercurial) tag associated with the previous ESR version when rebasing (ie, the ESR version we are rebasing from)
- `$(RR_VERSION)` : the Mozilla defined 'Rapid Relese' version, used in various places for building geckoview tags, labels, etc
    - example : `96.0.3`
- `$(RR_TAG)` : the Mozilla defined hg (Mercurial) tag associated with `$(ESR_VERSION)`
    - exmaple : `FIREFOX_96_0_3_RELEASE`
- `$(RR_TAG_PREV)` : the Mozilla defined hg (Mercurial) tag associated with the previous ESR version when rebasing (ie, the ESR version we are rebasing from)
- `$(TOR_BROWSER_MAJOR)` : the Tor Browser major version
    - example : `11`
- `$(TOR_BROWSER_MINOR)` : the Tor Browser minor version
    - example : either `0` or `5`; Alpha's is always `(Stable + 5) % 10`
- `$(FIREFOX_BUILD_N)` : the firefox build revision within a given `tor-browser` branch; this is separate from the `$(TOR_BROWSER_BUILD_N) ` value
    - example : `build1`
- `$(GECKOVIEW_BUILD_N)` : like `$(FIREFOX_BUILD_N)` but for geckoview branches
- `$(FENIX_BUILD_N)` : like `$(FIREFOX_BUILD_N)` but for fenix branches
- `$(TOR_BROWSER_BUILD_N)` : the tor-browser build revision for a given Tor Browser release; used in tagging git commits
    - example : `build2`
    - **NOTE** : `$(FIREFOX_BUILD_N)` and `$(TOR_BROWSER_BUILD_N)` typically are the same, but it is possible for them to diverge. For example :
        - if we have multiple Tor Browser releases on a given ESR branch the two will become out of sync as the `$(FIREFOX_BUILD_N)` value will increase, while the `$(TOR_BROWSER_BUILD_N)` value may stay at `build1` (but the `$(TOR_BROWSER_VERSION)` will increase)
        - if we have build failures unrelated to `tor-browser`, the `$(TOR_BROWSER_BUILD_N)` value will increase while the `$(FIREFOX_BUILD_N)` will stay the same.
- `$(TOR_BROWSER_VERSION)` : the published Tor Browser version
    - example : `11.5a6`, `11.0.7`
- `$(TOR_BROWSER_BRANCH)` : the full name of tor-browser branch
    - typically of the form: `tor-browser-$(ESR_VERSION)esr-$(TOR_BROWSER_MAJOR).$(TOR-BROWSER_MINOR)-1`
- `$(TOR_BROWSER_BRANCH_PREV)` : the full name of the previous tor-browser branch (when rebasing)
- `$(GECKOVIEW_BRANCH)` : the full name of geckoview branch
    - typically of the form: `tor-browser-$(RR_VERSION)-$(TOR_BROWSER_MAJOR).$(TOR-BROWSER_MINOR)-1`
- `$(GECKOVIEW_BRANCH_PREV)` : the full name of the previous geckoview branch (when rebasing)

    Desktop

### **torbutton** ***(Optional)***: https://git.torproject.org/torbutton.git
- [ ] ***(Optional)*** Update translations :
  - **NOTE** : mandatory if we have added new string dependencies
  -  [ ] `./import-translations.sh`
  -  [ ] Commit with message `Translation updates
     - **NOTE** : only add files which are already being tracked
  -  [ ] *(Optional)* Backport to maintenance branch if present
- [ ] fixup! `tor-browser`'s `Bug 10760 : Integrate TorButton to TorBrowser core` issue to point to updated `torbutton` commit

### **tor-launcher** ***(Optional)***: https://git.torproject.org/tor-launcher.git
- [ ] ***(Optional)*** Update translations:
  - **NOTE** : mandatory if we have added new string dependencies
  - [ ] ./localization/import-translations.sh
- [ ] Update `install.rdf` file with new version
- [ ] Sign/Tag commit :
    - Tag : `$(TOR_LAUNCHER_VERSION)`
    - Message `Tagging $(TOR_LAUNCHER_VERSION)`
- [ ] Push `main` and tag to origin

### tor-browser: https://git.torproject.org/tor-browser.git
- [ ] ***(Optional)*** Rebase to `$(ESR_VERSION)`
    - [ ] Find the Firefox hg tag here : https://hg.mozilla.org/releases/mozilla-esr91/tags
        - [ ] `$(ESR_TAG)` : `INSERT_TAG_HERE`
    - [ ] Identify the hg patch associated with above hg tag, and find the equivalent `gecko-dev` git commit (search by commit message)
        - [ ] `gecko-dev` commit : `INSERT_COMMIT_HASH_HERE`
    - [ ] Create new `tor-browser` branch with the discovered `gecko-dev` commit as `HEAD` named `tor-browser-$(ESR_VERSION)esr-$(TOR_BROWSER_MAJOR).$(TOR-BROWSER_MINOR)-1`
    - [ ] Sign/Tag commit :
        - Tag : `$(ESR_TAG)`
        - Message : `Hg tag $(ESR_TAG)`
    - [ ] Push new branch and tag to origin
    - [ ] Rebase `tor-browser` patches
    - [ ] Perform rangediff to ensure nothing weird happened resolving conflicts
        - `git range-diff $(ESR_TAG_PREV)..$(TOR_BROWSER_BRANCH_PREV) $(ESR_TAG)..$(TOR_BROWSER_BRANCH)`
    - [ ] Open MR for the rebase
- [ ] _TODO: tag base firefox no-tor browser_
- [ ] ***(Optional)*** Backport any required patches to Stable
    - [ ] cherry-pick patches on top of rebased branch (issues to backport should have `Backport` label and be linked to the associated `Release Prep` issue
    - [ ] Close associated `Backport` issues
    - [ ] Open MR for the backport commits
- [ ] Sign/Tag commit :
    - Tag : `tor-browser-$(ESR_VERSION)esr-$(TOR_BROWSER_MAJOR).$(TOR_BROWSER_MINOR)-1-$(FIREFOX_BUILD_N)`
    - Message : `Tagging $(FIREFOX_BUILD_N) for $(ESR_VERSION)esr-based (alpha|stable)`
- [ ] Push tag to origin

    Android

### **geckoview**: https://git.torproject.org/tor-browser.git
- [ ] ***(Optional)*** Rebase to `$(RR_VERSION)`
    - [ ] Find the Firefox hg tag here : https://hg.mozilla.org/releases/mozilla-release/tags
        - [ ] `$(RR_TAG)` : `INSERT_TAG_HERE`
    - [ ] Identify the hg patch associated with above hg tag, and find the equivalent `gecko-dev` git commit (search by commit message)
        - [ ] `gecko-dev` commit : `INSERT_COMMIT_HASH_HERE`
    - [ ] Create new `geckoview` branch with the discovered `gecko-dev` commit as `HEAD` named `geckoview-$(RR_VERSION)-$(TOR_BROWSER_MAJOR).$(TOR-BROWSER_MINOR)-1`
    - [ ] Sign/Tag commit :
        - Tag : `$(RR_TAG)`
        - Message : `Hg tag $(RR_TAG)`
    - [ ] Push new branch and tag to origin
    - [ ] Rebase `geckoview` patches
    - [ ] Perform rangediff to ensure nothing weird happened resolving conflicts
        - `git range-diff $(RR_TAG_PREV)..$(GECKOVIEW_BRANCH_PREV) $(RR_TAG)..$(GECKOVIEW_BRANCH)`
    - [ ] Open MR for the rebase
    - [ ] Merge + Push
- [ ] ***(Optional)*** Backport any required patches to Stable
    - [ ] cherry-pick patches on top of rebased branch (issues to backport should have `Backport` label and be linked to the associated `Release Prep` issue
    - [ ] Close associated `Backport` issues
    - [ ] Open MR for the backport commits
    - [ ] Merge + Push
- [ ] Sign/Tag commit :
    - Tag : `geckoview-$(RR_VERSION)-$(TOR_BROWSER_MAJOR).$(TOR_BROWSER_MINOR)-1-$(FIREFOX_BUILD_N)`
    - Message : `Tagging $(FIREFOX_BUILD_N) for $(RR_VERSION)-based (alpha|stable)`
- [ ] Push tag to origin

### **tba-translation** ***(Optional)***: https://git.torproject.org/translation.git
- [ ] Fetch latest and identify new HEAD of `fenix-torbrowserstringsxml` branch
  - [ ] `origin/fenix-torbrowserstringsxml` : `INSERT COMMIT HASH HERE`

### **android-components** ***(Optional)***: https://gitlab.torproject.org/tpo/applications/android-components.git
- [ ] ***(Optional)*** Rebase to `$(RR_VERSION)`
  - Upstream git repo : https://github.com/mozilla-mobile/android-components.git
  - [ ] Identify the `mozilla-mobile` git tag to start from
    - Seem to be in the form `v$(RR_VERSION)` (for example, `v99.0.3`)
  - [ ] Create new branch from tag named `android-components-$(RR_VERSION)-$(TOR_BROWSER_MAJOR).$(TOR_BROWSER_MINOR)-1`
  - [ ] Push new branch to origin
  - [ ] Rebase `android-components` patches
  - [ ] Perform rangediff to ensure nothing weird happened resolving conflicts
  - [ ] Open MR for the rebase
  - [ ] Merge + Push
 - [ ] ***(Optional)*** Backport any required patches to Stable
  - [ ] cherry-pick patches on top of rebased branch (issues to backport should have `Backport` label and be linked to the associated `Release Prep` issue
  - [ ] Close associated `Backport` issues
  - [ ] Open MR for the backport commits
  - [ ] Merge + Push
- [ ] Sign/Tag commit:
  - Tag : `android-components-$(RR_VERSION)-$(TOR_BROWSER_MAJOR).$(TOR_BROWSER_MINOR)-1-$(BUILD_N)`
  - Message: `Tagging $(BUILD_N) for $(RR_VERSION)-based (alpha|stable)`
- [ ] Push tag to origin

### **tor-android-service** ***(Optional)***: https://git.torproject.org/tor-android-service.git
- [ ] Fetch latest and identify new HEAD of `master` branch
  - [ ] `origin/master` : `INSERT COMMIT HASH HERE`

### **fenix** ***(Optional)***: https://gitlab.torproject.org/tpo/applications/fenix.git
- [ ] ***(Optional)*** Rebase to `$(RR_VERSION)`
  - Upstream git repo : https://github.com/mozilla-mobile/fenix.git
  - [ ] Identify the `mozilla-mobile` git tag to start from
    - Seem to be in the form `v$(RR_VERSION)` (for example, `v96.3.0`)
  - [ ] Create new branch from tag named `tor-browser-$(RR_VERSION)-$(TOR_BROWSER_MAJOR).$(TOR_BROWSER_MINOR)-1`
    - **NOTE** : it is weird but we do use `tor-browser` here rather than `fenix`
  - [ ] Push new branch to origin
  - [ ] Rebase `fenix` patches
  - [ ] Perform rangediff to ensure nothing weird happened resolving conflicts
  - [ ] Open MR for the rebase
  - [ ] Merge + Push
 - ***(Optional)*** Backport any required patches to Stable
- [ ] ***(Optional)*** Backport any required patches to Stable
  - [ ] cherry-pick patches on top of rebased branch (issues to backport should have `Backport` label and be linked to the associated `Release Prep` issue
  - [ ] Close associated `Backport` issues
  - [ ] Open MR for the backport commits
  - [ ] Merge + Push
- [ ] Sign/Tag commit:
  - Tag : `tor-browser-$(RR_VERSION)-$(TOR_BROWSER_MAJOR).$(TOR_BROWSER_MINOR)-1-$(BUILD_N)`
  - Message: `Tagging $(BUILD_N) for $(RR_VERSION)-based (alpha|stable)`
- [ ] Push tag to origin

    Build/Signing/Publishing

### tor-browser-build: https://git.torproject.org/builders/tor-browser-build.git
Tor Browser Alpha (and Nightly) are on the `main` branch, while Stable lives in the various `$(TOR_BROWSER_MAJOR).$(TOR_BROWSER_MINOR)-maint` (and possibly more specific) branches

- [ ] Update `rbm.conf`
    - [ ] `var/torbrowser_version` : update to next version
    - [ ] `var/torbrowser_build` : update to `$(TOR_BROWSER_BUILD_N)`
    - [ ] `var/torbrowser_incremental_from` : update to previous version
        - [ ] **IMPORTANT**: Really actually make sure this is the previous Desktop version or else the `make incrementals-*` step will fail
- [ ] Update `projects/firefox/config`
    - [ ] `git_hash` : update the `$(FIREFOX_BUILD_N)` section to match `tor-browser` tag
    - [ ] ***(Optional)*** `var/firefox_platform_version` : update to latest $(ESR_VERSION) if rebased
- [ ] ***(Android Only)*** Update `projects/geckoview/config`
    - [ ] `git_hash` : update the `$(GECKOVIEW_BUILD_N)` section to match `geckoview` tag
    - [ ] ***(Optional)*** `var/geckoview_version` : update to latest `$(RR_VERSION)` if rebased
- [ ] ***(Android Only, Optional)*** Update `projects/tba-translations/config`:
  - [ ]  `git_hash` : update with HEAD commit of project's `fenix-torbrowserstringsxml` branch
- [ ] ***(Android Only, Optional)*** Update `projects/tor-android-service/config`
  - [ ] `git_hash` : update with HEAD commit of project's `master` branch
- [ ] ***(Android Only, Optionl)*** Update `projects/fenix/config`
  - [ ] `git_hash` : update the `$(FENIX_BUILD_N)` section to match `fenix` tag
  - [ ] ***(Optional)*** `var/fenix_version` : update to latest `$(RR_VERSION)` if rebased
- [ ] ***(Android Only)*** Update allowed_addons.json by running (from `tor-browser-build` root)`./tools/fetch_allowed_addons.py > projects/tor-browser/allowed_addons.json
- [ ] Check for NoScript updates here : https://addons.mozilla.org/en-US/firefox/addon/noscript
    - [ ] ***(Optional)*** If version available, update `noscript` section of `input_files` in `projects/tor-browser/config`
        - [ ] `URL`
        - [ ] `sha256sum`
- [ ] Check for openssl updates here : https://github.com/openssl/openssl/tags
    - [ ] ***(Optional)*** If new 1.X.Y series tag available, update `projects/openssl/config`
        - [ ] `version` : update to next 1.X.Y release tag
        - [ ] `input_files/sha256sum` : update to sha256 sum of source tarball
- [ ] Check for tor updates here : https://gitlab.torproject.org/tpo/core/tor/-/tags ; Tor Browser Alpha uses `-alpha` tagged tor, while stable uses the stable series
    - [ ] ***(Optional)*** If new tor version is available, update `projects/tor/config`
        - [ ] `version` : update to next release tag
- [ ] Check for go updates here : https://golang.org/dl (Tor Browser Alpha uses the latest Stable go version, while Tor Browser Stable uses the latest of the previous Stable major series version (eg: if Tor Browser Alpha is on the go1.17 series, Tor Browser Stable is on the go1.16 series)
    - [ ] ***(Optional)*** If new go version is available, update `projects/go/config`
        - [ ] `version` : update go version
        - [ ] `input_files/sha256sum` for `go` : update sha256sum of archive (sha256 sums are displayed on the go download page)
- [ ] Update `ChangeLog.txt`
  - [ ] Ensure ChangeLog.txt is sync'd between alpha and stable branches
- [ ] Open MR with above changes
- [ ] Begin build on `$(BUILD_SERVER)`
- [ ] Sign/Tag commit : `make signtag-(alpha|release)`
- [ ] Push tag to origin

### notify stakeholders
- [ ] Email tor-qa mailing list: tor-qa@lists.torproject.org
    - [ ] Provide links to unsigned builds on `$(BUILD_SERVER)`
    - [ ] Call out any new functionality which needs testing
    - [ ] Link to any known issues
- [ ] Email Tails dev mailing list: tails-dev@boum.org
    - [ ] Provide links to unsigned builds on `$(BUILD_SERVER)`

### blog: https://gitlab.torproject.org/tpo/web/blog.git

- [ ] Duplicate previous Stable or Alpha release blog post as appropriate to new directory under `content/blog/new-release-tor-browser-$(TOR_BROWSER_VERSION)` and update with info on release :
    - [ ] Update Tor Browser version numbers
    - [ ] Note any ESR rebase
    - [ ] Note any Rapid Release rebase
    - [ ] Link to any Firefox security updates
    - [ ] Note any updates to :
        - [ ] tor
        - [ ] openssl
        - [ ] go
        - [ ] noscript
    - [ ] Convert ChangeLog.txt to markdown format used here by : `tor-browser-build/tools/changelog-format-blog-post`
- [ ] Push to origin as new branch, open 'Draft :' MR
- [ ] Remove draft from MR once signed-packages are uploaded
- [ ] Merge

### website: https://gitlab.torproject.org/tpo/web/tpo.git
- [ ] `databags/versions.ini` : Update the downloads versions
    - `torbrowser-stable/version` : sort of a catch-all for latest stable version
    - `torbrowser-stable/win32` : tor version in the expert bundle
    - `torbrowser-*-stable/version` : platform-specific stable versions
    - `torbrowser-*-alpha/version` : platform-specific alpha versions
    - `tor-stable`,`tor-alpha` : set by tor devs, do not touch
- [ ] Push to origin as new branch, open 'Draft :' MR
- [ ] Remove draft from MR once signed-packages are uploaded
- [ ] Merge

### signing + publishing
- [ ] Ensure builders have matching builds
- [ ] On `$(STAGING_SERVER)`, ensure updated:
  - [ ] `tor-browser-build/tools/signing/set-config`
    - [ ] `NSS_DB_DIR` : location of the `nssdb7` directory
  - [ ]  `tor-browser-build/tools/signing/set-config.hosts`
    - [ ] `ssh_host_builder` : ssh hostname of machine with unsigned builds
      - **NOTE** : `tor-browser-build` is expected to be in the `$HOME` directory)
    - [ ] `ssh_host_linux_signer` : ssh hostname of linux signing machine
    - [ ] `ssh_host_macos_signer` : ssh hostname of macOS signing machine
  - [ ] `tor-browser-build/tools/signing/set-config.macos-notarization`
    - [ ] `macos_notarization_user` : the email login for a tor notariser Apple Developer account
  - [ ] `tor-browser-build/tools/signing/set-config.tbb-version`
    - [ ] `tbb_version` : tor browser version string, same as `var/torbrowser_version` in `rbm.conf` (examples: `11.5a12`, `11.0.13`)
    - [ ] `tbb_version_build` : the tor-browser-build build number (if `var/torbrowser_build` in `rbm.conf` is `buildN` then this value is `N`)
    - [ ] `tbb_version_type` : either `alpha` for alpha releases or `release` for stable releases
- [ ] On `$(STAGING_SERVER)` in a separate `screen` session, run the macOS proxy script:
    - `cd tor-browser-build/tools/signing/`
    - `./macos-signer-proxy`
- [ ] On `$(STAGING_SERVER)` in a separate `screen` session, ensure tor daemon is running with SOCKS5 proxy on the default port 9050
- [ ] ***(Android Only)*** : *TODO*
- [ ] run do-all-signing script:
    - `cd tor-browser-build/tools/signing/`
    - `./do-all-signing.sh`
- **NOTE**: at this point the signed desktop binaries should have been copied to `staticiforme`
- [ ] Update `staticiforme.torproject.org`:
  - From `screen` session on `staticiforme.torproject.org`
  - [ ] Static update components : `static-update-component cdn.torproject.org && static-update-component dist.torproject.org`
  - [ ] Enable update responses :
    - [ ] alpha: `./deploy_update_responses-alpha.sh`
    - [ ] release: `./deploy_update_responses-release.sh`
- [ ] ***(Android Only)*** : Publish APKs to Google Play
  - [ ] Log into https://play.google.com/apps/publish
  - Select correct app:
    - [ ] Tor Browser
    - [ ] Tor Browser Alpha
  - [ ] Navigate to `Release > Production` and click `Create new release` button
  - [ ] Upload the `*.multi.apk` APKs
  - [ ] If necessary, update the 'Release Name' (should be automatically populated)
  - [ ] Update Release Notes
    - [ ] Next to 'Release notes', click `Copy from a previous release`
    - [ ] Edit blog post url to point to most recent blog post
  - [ ] Save, review, and configure rollout percentage
    - [ ] 25% rollout when publishing a scheduled update
    - [ ] 100% rollout when publishing a security-driven release
  - [ ] ***Optional*** Update  rollout percentage to 100% after confirmed no major issues

### tor-announce mailing list
- [ ] ***(Stable release only)*** : send an email to tor-announce@lists.torproject.org, using the same content as the blog post and subject "Tor Browser $version is released".

/label ~"Release Prep"

path: .gitlab/issue_templates/Release Prep.md


code:
[submodule "rbm"]
	path = rbm
	url = https://git.torproject.org/builders/rbm.git

path: .gitmodules


code:
rbm=./rbm/rbm

all: torbrowser-release

#######################
# Tor Browser Targets #
#######################

torbrowser-release: submodule-update
	$(rbm) build release --target release --target browser-all --target torbrowser

torbrowser-release-android: submodule-update
	$(rbm) build release --target release --target browser-all-android --target torbrowser

torbrowser-release-android-armv7: submodule-update
	$(rbm) build release --target release --target browser-android-armv7 --target torbrowser

torbrowser-release-android-x86: submodule-update
	$(rbm) build release --target release --target browser-android-x86 --target torbrowser

torbrowser-release-android-x86_64: submodule-update
	$(rbm) build release --target release --target browser-android-x86_64 --target torbrowser

torbrowser-release-android-aarch64: submodule-update
	$(rbm) build release --target release --target browser-android-aarch64 --target torbrowser

torbrowser-release-desktop: submodule-update
	$(rbm) build release --target release --target browser-all-desktop --target torbrowser

torbrowser-release-linux-x86_64: submodule-update
	$(rbm) build release --target release --target browser-linux-x86_64 --target torbrowser

torbrowser-release-linux-x86_64-asan: submodule-update
	$(rbm) build release --target release --target browser-linux-x86_64-asan --target torbrowser

torbrowser-release-linux-i686: submodule-update
	$(rbm) build release --target release --target browser-linux-i686 --target torbrowser

torbrowser-release-windows-i686: submodule-update
	$(rbm) build release --target release --target browser-windows-i686 --target torbrowser

torbrowser-release-windows-x86_64: submodule-update
	$(rbm) build release --target release --target browser-windows-x86_64 --target torbrowser

torbrowser-release-osx-x86_64: submodule-update
	$(rbm) build release --target release --target browser-osx-x86_64 --target torbrowser

torbrowser-release-src: submodule-update
	$(rbm) build release --target release --target browser-src --target torbrowser

torbrowser-alpha: submodule-update
	$(rbm) build release --target alpha --target browser-all --target torbrowser

torbrowser-alpha-android: submodule-update
	$(rbm) build release --target alpha --target browser-all-android --target torbrowser

torbrowser-alpha-android-armv7: submodule-update
	$(rbm) build release --target alpha --target browser-android-armv7 --target torbrowser

torbrowser-alpha-android-x86: submodule-update
	$(rbm) build release --target alpha --target browser-android-x86 --target torbrowser

torbrowser-alpha-android-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-android-x86_64 --target torbrowser

torbrowser-alpha-android-aarch64: submodule-update
	$(rbm) build release --target alpha --target browser-android-aarch64 --target torbrowser

torbrowser-alpha-desktop: submodule-update
	$(rbm) build release --target alpha --target browser-all-desktop --target torbrowser

torbrowser-alpha-linux-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-linux-x86_64 --target torbrowser

torbrowser-alpha-linux-x86_64-asan: submodule-update
	$(rbm) build release --target alpha --target browser-linux-x86_64-asan --target torbrowser

torbrowser-alpha-linux-i686: submodule-update
	$(rbm) build release --target alpha --target browser-linux-i686 --target torbrowser

torbrowser-alpha-windows-i686: submodule-update
	$(rbm) build release --target alpha --target browser-windows-i686 --target torbrowser

torbrowser-alpha-windows-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-windows-x86_64 --target torbrowser

torbrowser-alpha-osx-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-osx-x86_64 --target torbrowser

torbrowser-alpha-src: submodule-update
	$(rbm) build release --target alpha --target browser-src --target torbrowser

torbrowser-nightly: submodule-update
	$(rbm) build release --target nightly --target browser-all --target torbrowser

torbrowser-nightly-android: submodule-update
	$(rbm) build release --target nightly --target browser-all-android --target torbrowser

torbrowser-nightly-android-armv7: submodule-update
	$(rbm) build release --target nightly --target browser-android-armv7 --target torbrowser

torbrowser-nightly-android-x86: submodule-update
	$(rbm) build release --target nightly --target browser-android-x86 --target torbrowser

torbrowser-nightly-android-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-android-x86_64 --target torbrowser

torbrowser-nightly-android-aarch64: submodule-update
	$(rbm) build release --target nightly --target browser-android-aarch64 --target torbrowser

torbrowser-nightly-desktop: submodule-update
	$(rbm) build release --target nightly --target browser-all-desktop --target torbrowser

torbrowser-nightly-linux-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-linux-x86_64 --target torbrowser

torbrowser-nightly-linux-x86_64-asan: submodule-update
	$(rbm) build release --target nightly --target browser-linux-x86_64-asan --target torbrowser

torbrowser-nightly-linux-i686: submodule-update
	$(rbm) build release --target nightly --target browser-linux-i686 --target torbrowser

torbrowser-nightly-windows-i686: submodule-update
	$(rbm) build release --target nightly --target browser-windows-i686 --target torbrowser

torbrowser-nightly-windows-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-windows-x86_64 --target torbrowser

torbrowser-nightly-osx-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-osx-x86_64 --target torbrowser

torbrowser-nightly-src: submodule-update
	$(rbm) build release --target nightly --target browser-src --target torbrowser

torbrowser-testbuild: submodule-update
	$(rbm) build release --target testbuild --target browser-all --target torbrowser

torbrowser-testbuild-android: submodule-update
	$(rbm) build release --target testbuild --target browser-all-android --target torbrowser

torbrowser-testbuild-android-armv7: submodule-update
	$(rbm) build release --target testbuild --target browser-android-armv7 --target torbrowser

torbrowser-testbuild-android-x86: submodule-update
	$(rbm) build release --target testbuild --target browser-android-x86 --target torbrowser

torbrowser-testbuild-android-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-android-x86_64 --target torbrowser

torbrowser-testbuild-android-aarch64: submodule-update
	$(rbm) build release --target testbuild --target browser-android-aarch64 --target torbrowser

torbrowser-testbuild-desktop: submodule-update
	$(rbm) build release --target testbuild --target browser-all-desktop --target torbrowser

torbrowser-testbuild-linux-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-linux-x86_64 --target torbrowser

torbrowser-testbuild-linux-x86_64-asan: submodule-update
	$(rbm) build release --target testbuild --target browser-linux-x86_64-asan --target torbrowser

torbrowser-testbuild-linux-i686: submodule-update
	$(rbm) build release --target testbuild --target browser-linux-i686 --target torbrowser

torbrowser-testbuild-windows-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-windows-x86_64 --target torbrowser

torbrowser-testbuild-windows-i686: submodule-update
	$(rbm) build release --target testbuild --target browser-windows-i686 --target torbrowser

torbrowser-testbuild-osx-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-osx-x86_64 --target torbrowser

torbrowser-testbuild-src: submodule-update
	$(rbm) build release --target testbuild --target browser-src-testbuild --target torbrowser

signtag-release: submodule-update
	$(rbm) build release --step signtag --target release

signtag-alpha: submodule-update
	$(rbm) build release --step signtag --target alpha

incrementals-release: submodule-update
	$(rbm) build release --step update_responses_config --target release --target create_unsigned_incrementals
	tools/update-responses/download_missing_versions release
	tools/update-responses/gen_incrementals release
	$(rbm) build release --step hash_incrementals --target release

incrementals-alpha: submodule-update
	$(rbm) build release --step update_responses_config --target alpha --target create_unsigned_incrementals
	tools/update-responses/download_missing_versions alpha
	tools/update-responses/gen_incrementals alpha
	$(rbm) build release --step hash_incrementals --target alpha

incrementals-nightly: submodule-update
	$(rbm) build release --step update_responses_config --target nightly
	NO_CODESIGNATURE=1 tools/update-responses/gen_incrementals nightly
	$(rbm) build release --step hash_incrementals --target nightly

update_responses-release: submodule-update
	$(rbm) build release --step update_responses_config --target release --target signed
	$(rbm) build release --step create_update_responses_tar --target release --target signed

update_responses-alpha: submodule-update
	$(rbm) build release --step update_responses_config --target alpha --target signed
	$(rbm) build release --step create_update_responses_tar --target alpha --target signed

dmg2mar-release: submodule-update
	$(rbm) build release --step update_responses_config --target release --target signed
	$(rbm) build release --step dmg2mar --target release --target signed
	tools/update-responses/download_missing_versions release
	CHECK_CODESIGNATURE_EXISTS=1 MAR_SKIP_EXISTING=1 tools/update-responses/gen_incrementals release

dmg2mar-alpha: submodule-update
	$(rbm) build release --step update_responses_config --target alpha --target signed
	$(rbm) build release --step dmg2mar --target alpha --target signed
	tools/update-responses/download_missing_versions alpha
	CHECK_CODESIGNATURE_EXISTS=1 MAR_SKIP_EXISTING=1 tools/update-responses/gen_incrementals alpha

list_toolchain_updates-fenix: submodule-update
	$(rbm) build fenix --step list_toolchain_updates --target nightly --target torbrowser-android-armv7

list_toolchain_updates-firefox-linux: submodule-update
	$(rbm) build firefox --step list_toolchain_updates --target nightly --target torbrowser-linux-x86_64

list_toolchain_updates-firefox-windows: submodule-update
	$(rbm) build firefox --step list_toolchain_updates --target nightly --target torbrowser-windows-x86_64

list_toolchain_updates-firefox-macos: submodule-update
	$(rbm) build firefox --step list_toolchain_updates --target nightly --target torbrowser-osx-x86_64

list_toolchain_updates-android-components: submodule-update
	$(rbm) build android-components --step list_toolchain_updates --target nightly --target torbrowser-android-armv7

list_toolchain_updates-application-services: submodule-update
	$(rbm) build application-services --step list_toolchain_updates --target nightly --target torbrowser-android-armv7

list_toolchain_updates-geckoview: submodule-update
	$(rbm) build geckoview --step list_toolchain_updates --target nightly --target torbrowser-android-armv7

create_glean_deps_tarball: submodule-update
	 $(rbm) build glean --step create_glean_deps_tarball --target alpha --target torbrowser-android-armv7

create_glean_deps_tarball-with_torsocks: submodule-update
	$(rbm) build glean --step create_glean_deps_tarball --target alpha --target torbrowser-android-armv7 --target with_torsocks

get_gradle_dependencies_list-fenix: submodule-update
	$(rbm) build fenix --step get_gradle_dependencies_list --target nightly --target torbrowser-android-armv7

get_gradle_dependencies_list-application-services: submodule-update
	$(rbm) build application-services --step get_gradle_dependencies_list --target nightly --target torbrowser-android-armv7

get_gradle_dependencies_list-android-components: submodule-update
	$(rbm) build android-components --step get_gradle_dependencies_list --target nightly --target torbrowser-android-armv7

cargo_vendor-application-services: submodule-update
	$(rbm) build application-services --step cargo_vendor --target nightly --target torbrowser-android-armv7

cargo_vendor-cbindgen-android: submodule-update
	$(rbm) build cbindgen --step cargo_vendor --target nightly --target torbrowser-android-armv7

cargo_vendor-cbindgen: submodule-update
	$(rbm) build cbindgen --step cargo_vendor --target nightly --target torbrowser-linux-x86_64

cargo_vendor-lucetc: submodule-update
	$(rbm) build lucetc --step cargo_vendor --target nightly --target torbrowser-linux-x86_64

cargo_vendor-uniffi-rs: submodule-update
	$(rbm) build uniffi-rs --step cargo_vendor --target nightly --target torbrowser-linux-x86_64

########################
# Base Browser Targets #
########################

basebrowser-release: submodule-update
	$(rbm) build release --target release --target browser-all --target basebrowser

basebrowser-release-android: submodule-update
	$(rbm) build release --target release --target browser-all-android --target basebrowser

basebrowser-release-android-armv7: submodule-update
	$(rbm) build release --target release --target browser-android-armv7 --target basebrowser

basebrowser-release-android-x86: submodule-update
	$(rbm) build release --target release --target browser-android-x86 --target basebrowser

basebrowser-release-android-x86_64: submodule-update
	$(rbm) build release --target release --target browser-android-x86_64 --target basebrowser

basebrowser-release-android-aarch64: submodule-update
	$(rbm) build release --target release --target browser-android-aarch64 --target basebrowser

basebrowser-release-desktop: submodule-update
	$(rbm) build release --target release --target browser-all-desktop --target basebrowser

basebrowser-release-linux-x86_64: submodule-update
	$(rbm) build release --target release --target browser-linux-x86_64 --target basebrowser

basebrowser-release-linux-x86_64-asan: submodule-update
	$(rbm) build release --target release --target browser-linux-x86_64-asan --target basebrowser

basebrowser-release-linux-i686: submodule-update
	$(rbm) build release --target release --target browser-linux-i686 --target basebrowser

basebrowser-release-windows-i686: submodule-update
	$(rbm) build release --target release --target browser-windows-i686 --target basebrowser

basebrowser-release-windows-x86_64: submodule-update
	$(rbm) build release --target release --target browser-windows-x86_64 --target basebrowser

basebrowser-release-osx-x86_64: submodule-update
	$(rbm) build release --target release --target browser-osx-x86_64 --target basebrowser

basebrowser-release-src: submodule-update
	$(rbm) build release --target release --target browser-src --target basebrowser

basebrowser-alpha: submodule-update
	$(rbm) build release --target alpha --target browser-all --target basebrowser

basebrowser-alpha-android: submodule-update
	$(rbm) build release --target alpha --target browser-all-android --target basebrowser

basebrowser-alpha-android-armv7: submodule-update
	$(rbm) build release --target alpha --target browser-android-armv7 --target basebrowser

basebrowser-alpha-android-x86: submodule-update
	$(rbm) build release --target alpha --target browser-android-x86 --target basebrowser

basebrowser-alpha-android-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-android-x86_64 --target basebrowser

basebrowser-alpha-android-aarch64: submodule-update
	$(rbm) build release --target alpha --target browser-android-aarch64 --target basebrowser

basebrowser-alpha-desktop: submodule-update
	$(rbm) build release --target alpha --target browser-all-desktop --target basebrowser

basebrowser-alpha-linux-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-linux-x86_64 --target basebrowser

basebrowser-alpha-linux-x86_64-asan: submodule-update
	$(rbm) build release --target alpha --target browser-linux-x86_64-asan --target basebrowser

basebrowser-alpha-linux-i686: submodule-update
	$(rbm) build release --target alpha --target browser-linux-i686 --target basebrowser

basebrowser-alpha-windows-i686: submodule-update
	$(rbm) build release --target alpha --target browser-windows-i686 --target basebrowser

basebrowser-alpha-windows-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-windows-x86_64 --target basebrowser

basebrowser-alpha-osx-x86_64: submodule-update
	$(rbm) build release --target alpha --target browser-osx-x86_64 --target basebrowser

basebrowser-alpha-src: submodule-update
	$(rbm) build release --target alpha --target browser-src --target basebrowser

basebrowser-nightly: submodule-update
	$(rbm) build release --target nightly --target browser-all --target basebrowser

basebrowser-nightly-android: submodule-update
	$(rbm) build release --target nightly --target browser-all-android --target basebrowser

basebrowser-nightly-android-armv7: submodule-update
	$(rbm) build release --target nightly --target browser-android-armv7 --target basebrowser

basebrowser-nightly-android-x86: submodule-update
	$(rbm) build release --target nightly --target browser-android-x86 --target basebrowser

basebrowser-nightly-android-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-android-x86_64 --target basebrowser

basebrowser-nightly-android-aarch64: submodule-update
	$(rbm) build release --target nightly --target browser-android-aarch64 --target basebrowser

basebrowser-nightly-desktop: submodule-update
	$(rbm) build release --target nightly --target browser-all-desktop --target basebrowser

basebrowser-nightly-linux-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-linux-x86_64 --target basebrowser

basebrowser-nightly-linux-x86_64-asan: submodule-update
	$(rbm) build release --target nightly --target browser-linux-x86_64-asan --target basebrowser

basebrowser-nightly-linux-i686: submodule-update
	$(rbm) build release --target nightly --target browser-linux-i686 --target basebrowser

basebrowser-nightly-windows-i686: submodule-update
	$(rbm) build release --target nightly --target browser-windows-i686 --target basebrowser

basebrowser-nightly-windows-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-windows-x86_64 --target basebrowser

basebrowser-nightly-osx-x86_64: submodule-update
	$(rbm) build release --target nightly --target browser-osx-x86_64 --target basebrowser

basebrowser-nightly-src: submodule-update
	$(rbm) build release --target nightly --target browser-src --target basebrowser

basebrowser-testbuild: submodule-update
	$(rbm) build release --target testbuild --target browser-all --target basebrowser

basebrowser-testbuild-android: submodule-update
	$(rbm) build release --target testbuild --target browser-all-android --target basebrowser

basebrowser-testbuild-android-armv7: submodule-update
	$(rbm) build release --target testbuild --target browser-android-armv7 --target basebrowser

basebrowser-testbuild-android-x86: submodule-update
	$(rbm) build release --target testbuild --target browser-android-x86 --target basebrowser

basebrowser-testbuild-android-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-android-x86_64 --target basebrowser

basebrowser-testbuild-android-aarch64: submodule-update
	$(rbm) build release --target testbuild --target browser-android-aarch64 --target basebrowser

basebrowser-testbuild-desktop: submodule-update
	$(rbm) build release --target testbuild --target browser-all-desktop --target basebrowser

basebrowser-testbuild-linux-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-linux-x86_64 --target basebrowser

basebrowser-testbuild-linux-x86_64-asan: submodule-update
	$(rbm) build release --target testbuild --target browser-linux-x86_64-asan --target basebrowser

basebrowser-testbuild-linux-i686: submodule-update
	$(rbm) build release --target testbuild --target browser-linux-i686 --target basebrowser

basebrowser-testbuild-windows-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-windows-x86_64 --target basebrowser

basebrowser-testbuild-windows-i686: submodule-update
	$(rbm) build release --target testbuild --target browser-windows-i686 --target basebrowser

basebrowser-testbuild-osx-x86_64: submodule-update
	$(rbm) build release --target testbuild --target browser-osx-x86_64 --target basebrowser

basebrowser-testbuild-src: submodule-update
	$(rbm) build release --target testbuild --target browser-src-testbuild --target basebrowser

##################
# Common Targets #
##################

submodule-update:
	git submodule update --init

fetch: submodule-update
	$(rbm) fetch

clean: submodule-update
	./tools/clean-old

clean-dry-run: submodule-update
	./tools/clean-old --dry-run