meme-generator-next 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. meme_generator_next-0.2.0/LICENSE +21 -0
  2. meme_generator_next-0.2.0/PKG-INFO +169 -0
  3. meme_generator_next-0.2.0/README.md +129 -0
  4. meme_generator_next-0.2.0/meme_generator/__init__.py +59 -0
  5. meme_generator_next-0.2.0/meme_generator/app.py +322 -0
  6. meme_generator_next-0.2.0/meme_generator/cli.py +238 -0
  7. meme_generator_next-0.2.0/meme_generator/compat.py +96 -0
  8. meme_generator_next-0.2.0/meme_generator/config.py +98 -0
  9. meme_generator_next-0.2.0/meme_generator/dirs.py +251 -0
  10. meme_generator_next-0.2.0/meme_generator/download.py +116 -0
  11. meme_generator_next-0.2.0/meme_generator/exception.py +120 -0
  12. meme_generator_next-0.2.0/meme_generator/log.py +87 -0
  13. meme_generator_next-0.2.0/meme_generator/manager.py +111 -0
  14. meme_generator_next-0.2.0/meme_generator/meme.py +167 -0
  15. meme_generator_next-0.2.0/meme_generator/memes/5000choyen/__init__.py +202 -0
  16. meme_generator_next-0.2.0/meme_generator/memes/abstinence/__init__.py +93 -0
  17. meme_generator_next-0.2.0/meme_generator/memes/ace_attorney_dialog/__init__.py +122 -0
  18. meme_generator_next-0.2.0/meme_generator/memes/acg_entrance/__init__.py +47 -0
  19. meme_generator_next-0.2.0/meme_generator/memes/add_chaos/__init__.py +29 -0
  20. meme_generator_next-0.2.0/meme_generator/memes/addiction/__init__.py +41 -0
  21. meme_generator_next-0.2.0/meme_generator/memes/alike/__init__.py +32 -0
  22. meme_generator_next-0.2.0/meme_generator/memes/alipay/__init__.py +89 -0
  23. meme_generator_next-0.2.0/meme_generator/memes/always/__init__.py +138 -0
  24. meme_generator_next-0.2.0/meme_generator/memes/always_like/__init__.py +96 -0
  25. meme_generator_next-0.2.0/meme_generator/memes/anti_kidnap/__init__.py +26 -0
  26. meme_generator_next-0.2.0/meme_generator/memes/anya_suki/__init__.py +50 -0
  27. meme_generator_next-0.2.0/meme_generator/memes/applaud/__init__.py +39 -0
  28. meme_generator_next-0.2.0/meme_generator/memes/arona_throw/__init__.py +64 -0
  29. meme_generator_next-0.2.0/meme_generator/memes/ascension/__init__.py +37 -0
  30. meme_generator_next-0.2.0/meme_generator/memes/ask/__init__.py +94 -0
  31. meme_generator_next-0.2.0/meme_generator/memes/atri_pillow/__init__.py +75 -0
  32. meme_generator_next-0.2.0/meme_generator/memes/ayachi_holdsign/__init__.py +49 -0
  33. meme_generator_next-0.2.0/meme_generator/memes/ba_say/__init__.py +144 -0
  34. meme_generator_next-0.2.0/meme_generator/memes/back_to_work/__init__.py +28 -0
  35. meme_generator_next-0.2.0/meme_generator/memes/backflip/__init__.py +81 -0
  36. meme_generator_next-0.2.0/meme_generator/memes/bad_news/__init__.py +41 -0
  37. meme_generator_next-0.2.0/meme_generator/memes/beat_head/__init__.py +52 -0
  38. meme_generator_next-0.2.0/meme_generator/memes/beat_up/__init__.py +38 -0
  39. meme_generator_next-0.2.0/meme_generator/memes/bite/__init__.py +41 -0
  40. meme_generator_next-0.2.0/meme_generator/memes/blamed_mahiro/__init__.py +78 -0
  41. meme_generator_next-0.2.0/meme_generator/memes/blood_pressure/__init__.py +30 -0
  42. meme_generator_next-0.2.0/meme_generator/memes/bluearchive/__init__.py +67 -0
  43. meme_generator_next-0.2.0/meme_generator/memes/bocchi_draft/__init__.py +52 -0
  44. meme_generator_next-0.2.0/meme_generator/memes/bronya_holdsign/__init__.py +41 -0
  45. meme_generator_next-0.2.0/meme_generator/memes/bubble_tea/__init__.py +76 -0
  46. meme_generator_next-0.2.0/meme_generator/memes/call_110/__init__.py +28 -0
  47. meme_generator_next-0.2.0/meme_generator/memes/capoo_draw/__init__.py +47 -0
  48. meme_generator_next-0.2.0/meme_generator/memes/capoo_point/__init__.py +40 -0
  49. meme_generator_next-0.2.0/meme_generator/memes/capoo_rip/__init__.py +63 -0
  50. meme_generator_next-0.2.0/meme_generator/memes/capoo_rub/__init__.py +40 -0
  51. meme_generator_next-0.2.0/meme_generator/memes/capoo_say/__init__.py +71 -0
  52. meme_generator_next-0.2.0/meme_generator/memes/capoo_stew/__init__.py +40 -0
  53. meme_generator_next-0.2.0/meme_generator/memes/capoo_strike/__init__.py +48 -0
  54. meme_generator_next-0.2.0/meme_generator/memes/captain/__init__.py +39 -0
  55. meme_generator_next-0.2.0/meme_generator/memes/caused_by_this/__init__.py +66 -0
  56. meme_generator_next-0.2.0/meme_generator/memes/certificate/__init__.py +125 -0
  57. meme_generator_next-0.2.0/meme_generator/memes/charpic/__init__.py +43 -0
  58. meme_generator_next-0.2.0/meme_generator/memes/chase_train/__init__.py +66 -0
  59. meme_generator_next-0.2.0/meme_generator/memes/china_flag/__init__.py +26 -0
  60. meme_generator_next-0.2.0/meme_generator/memes/chino_throw/__init__.py +60 -0
  61. meme_generator_next-0.2.0/meme_generator/memes/clauvio_twist/__init__.py +58 -0
  62. meme_generator_next-0.2.0/meme_generator/memes/clown/__init__.py +91 -0
  63. meme_generator_next-0.2.0/meme_generator/memes/clown_mask/__init__.py +86 -0
  64. meme_generator_next-0.2.0/meme_generator/memes/confuse/__init__.py +40 -0
  65. meme_generator_next-0.2.0/meme_generator/memes/coupon/__init__.py +45 -0
  66. meme_generator_next-0.2.0/meme_generator/memes/cover_face/__init__.py +27 -0
  67. meme_generator_next-0.2.0/meme_generator/memes/crawl/__init__.py +63 -0
  68. meme_generator_next-0.2.0/meme_generator/memes/cyan/__init__.py +39 -0
  69. meme_generator_next-0.2.0/meme_generator/memes/daynight/__init__.py +28 -0
  70. meme_generator_next-0.2.0/meme_generator/memes/decent_kiss/__init__.py +26 -0
  71. meme_generator_next-0.2.0/meme_generator/memes/dianzhongdian/__init__.py +62 -0
  72. meme_generator_next-0.2.0/meme_generator/memes/dinosaur/__init__.py +30 -0
  73. meme_generator_next-0.2.0/meme_generator/memes/distracted/__init__.py +33 -0
  74. meme_generator_next-0.2.0/meme_generator/memes/divorce/__init__.py +26 -0
  75. meme_generator_next-0.2.0/meme_generator/memes/dog_dislike/__init__.py +94 -0
  76. meme_generator_next-0.2.0/meme_generator/memes/dog_of_vtb/__init__.py +31 -0
  77. meme_generator_next-0.2.0/meme_generator/memes/dont_go_near/__init__.py +30 -0
  78. meme_generator_next-0.2.0/meme_generator/memes/dont_press/__init__.py +38 -0
  79. meme_generator_next-0.2.0/meme_generator/memes/dont_touch/__init__.py +66 -0
  80. meme_generator_next-0.2.0/meme_generator/memes/douyin/__init__.py +80 -0
  81. meme_generator_next-0.2.0/meme_generator/memes/eat/__init__.py +30 -0
  82. meme_generator_next-0.2.0/meme_generator/memes/emperor_dragon/__init__.py +40 -0
  83. meme_generator_next-0.2.0/meme_generator/memes/erised_mirror/__init__.py +39 -0
  84. meme_generator_next-0.2.0/meme_generator/memes/fade_away/__init__.py +134 -0
  85. meme_generator_next-0.2.0/meme_generator/memes/fanatic/__init__.py +38 -0
  86. meme_generator_next-0.2.0/meme_generator/memes/father_work/__init__.py +49 -0
  87. meme_generator_next-0.2.0/meme_generator/memes/fencing/__init__.py +45 -0
  88. meme_generator_next-0.2.0/meme_generator/memes/fight_with_sunuo/__init__.py +32 -0
  89. meme_generator_next-0.2.0/meme_generator/memes/fill_head/__init__.py +48 -0
  90. meme_generator_next-0.2.0/meme_generator/memes/find_chips/__init__.py +44 -0
  91. meme_generator_next-0.2.0/meme_generator/memes/firefly_holdsign/__init__.py +105 -0
  92. meme_generator_next-0.2.0/meme_generator/memes/flash_blind/__init__.py +65 -0
  93. meme_generator_next-0.2.0/meme_generator/memes/flick/__init__.py +109 -0
  94. meme_generator_next-0.2.0/meme_generator/memes/flush/__init__.py +55 -0
  95. meme_generator_next-0.2.0/meme_generator/memes/fogging/__init__.py +51 -0
  96. meme_generator_next-0.2.0/meme_generator/memes/follow/__init__.py +46 -0
  97. meme_generator_next-0.2.0/meme_generator/memes/frieren_take/__init__.py +50 -0
  98. meme_generator_next-0.2.0/meme_generator/memes/funny_mirror/__init__.py +31 -0
  99. meme_generator_next-0.2.0/meme_generator/memes/garbage/__init__.py +38 -0
  100. meme_generator_next-0.2.0/meme_generator/memes/genshin_eat/__init__.py +99 -0
  101. meme_generator_next-0.2.0/meme_generator/memes/genshin_start/__init__.py +61 -0
  102. meme_generator_next-0.2.0/meme_generator/memes/gif_subtitle/__init__.py +197 -0
  103. meme_generator_next-0.2.0/meme_generator/memes/good_news/__init__.py +41 -0
  104. meme_generator_next-0.2.0/meme_generator/memes/google/__init__.py +29 -0
  105. meme_generator_next-0.2.0/meme_generator/memes/google_captcha/__init__.py +78 -0
  106. meme_generator_next-0.2.0/meme_generator/memes/gorilla_throw/__init__.py +58 -0
  107. meme_generator_next-0.2.0/meme_generator/memes/guichu/__init__.py +129 -0
  108. meme_generator_next-0.2.0/meme_generator/memes/gun/__init__.py +76 -0
  109. meme_generator_next-0.2.0/meme_generator/memes/hammer/__init__.py +38 -0
  110. meme_generator_next-0.2.0/meme_generator/memes/haruhi_raise/__init__.py +36 -0
  111. meme_generator_next-0.2.0/meme_generator/memes/high_EQ/__init__.py +56 -0
  112. meme_generator_next-0.2.0/meme_generator/memes/hit_screen/__init__.py +56 -0
  113. meme_generator_next-0.2.0/meme_generator/memes/hold_grudge/__init__.py +37 -0
  114. meme_generator_next-0.2.0/meme_generator/memes/hold_tight/__init__.py +26 -0
  115. meme_generator_next-0.2.0/meme_generator/memes/hug/__init__.py +45 -0
  116. meme_generator_next-0.2.0/meme_generator/memes/hug_leg/__init__.py +40 -0
  117. meme_generator_next-0.2.0/meme_generator/memes/hutao_bite/__init__.py +35 -0
  118. meme_generator_next-0.2.0/meme_generator/memes/imprison/__init__.py +37 -0
  119. meme_generator_next-0.2.0/meme_generator/memes/incivilization/__init__.py +47 -0
  120. meme_generator_next-0.2.0/meme_generator/memes/intel_inside/__init__.py +47 -0
  121. meme_generator_next-0.2.0/meme_generator/memes/interview/__init__.py +49 -0
  122. meme_generator_next-0.2.0/meme_generator/memes/jerry_stare/__init__.py +32 -0
  123. meme_generator_next-0.2.0/meme_generator/memes/jiji_king/__init__.py +117 -0
  124. meme_generator_next-0.2.0/meme_generator/memes/jinhsi/__init__.py +81 -0
  125. meme_generator_next-0.2.0/meme_generator/memes/jiubingfufa/__init__.py +53 -0
  126. meme_generator_next-0.2.0/meme_generator/memes/jiujiu/__init__.py +31 -0
  127. meme_generator_next-0.2.0/meme_generator/memes/jump/__init__.py +41 -0
  128. meme_generator_next-0.2.0/meme_generator/memes/kaleidoscope/__init__.py +73 -0
  129. meme_generator_next-0.2.0/meme_generator/memes/karyl_point/__init__.py +28 -0
  130. meme_generator_next-0.2.0/meme_generator/memes/keep_away/__init__.py +51 -0
  131. meme_generator_next-0.2.0/meme_generator/memes/keep_your_money/__init__.py +70 -0
  132. meme_generator_next-0.2.0/meme_generator/memes/kick_ball/__init__.py +38 -0
  133. meme_generator_next-0.2.0/meme_generator/memes/kirby_hammer/__init__.py +80 -0
  134. meme_generator_next-0.2.0/meme_generator/memes/kiss/__init__.py +43 -0
  135. meme_generator_next-0.2.0/meme_generator/memes/klee_eat/__init__.py +43 -0
  136. meme_generator_next-0.2.0/meme_generator/memes/knock/__init__.py +38 -0
  137. meme_generator_next-0.2.0/meme_generator/memes/kokona_seal/__init__.py +81 -0
  138. meme_generator_next-0.2.0/meme_generator/memes/konata_watch/__init__.py +36 -0
  139. meme_generator_next-0.2.0/meme_generator/memes/learn/__init__.py +47 -0
  140. meme_generator_next-0.2.0/meme_generator/memes/left_right_jump/__init__.py +105 -0
  141. meme_generator_next-0.2.0/meme_generator/memes/let_me_in/__init__.py +30 -0
  142. meme_generator_next-0.2.0/meme_generator/memes/lick_candy/__init__.py +131 -0
  143. meme_generator_next-0.2.0/meme_generator/memes/lim_x_0/__init__.py +43 -0
  144. meme_generator_next-0.2.0/meme_generator/memes/listen_music/__init__.py +34 -0
  145. meme_generator_next-0.2.0/meme_generator/memes/little_angel/__init__.py +57 -0
  146. meme_generator_next-0.2.0/meme_generator/memes/loading/__init__.py +44 -0
  147. meme_generator_next-0.2.0/meme_generator/memes/look_flat/__init__.py +79 -0
  148. meme_generator_next-0.2.0/meme_generator/memes/look_this_icon/__init__.py +48 -0
  149. meme_generator_next-0.2.0/meme_generator/memes/loop/__init__.py +108 -0
  150. meme_generator_next-0.2.0/meme_generator/memes/lost_dog/__init__.py +43 -0
  151. meme_generator_next-0.2.0/meme_generator/memes/love_you/__init__.py +34 -0
  152. meme_generator_next-0.2.0/meme_generator/memes/luotianyi_need/__init__.py +34 -0
  153. meme_generator_next-0.2.0/meme_generator/memes/luotianyi_say/__init__.py +44 -0
  154. meme_generator_next-0.2.0/meme_generator/memes/luoyonghao_say/__init__.py +44 -0
  155. meme_generator_next-0.2.0/meme_generator/memes/luxun_say/__init__.py +39 -0
  156. meme_generator_next-0.2.0/meme_generator/memes/mahiro_readbook/__init__.py +45 -0
  157. meme_generator_next-0.2.0/meme_generator/memes/maimai_awaken/__init__.py +34 -0
  158. meme_generator_next-0.2.0/meme_generator/memes/maimai_join/__init__.py +32 -0
  159. meme_generator_next-0.2.0/meme_generator/memes/make_friend/__init__.py +52 -0
  160. meme_generator_next-0.2.0/meme_generator/memes/marriage/__init__.py +35 -0
  161. meme_generator_next-0.2.0/meme_generator/memes/meteor/__init__.py +38 -0
  162. meme_generator_next-0.2.0/meme_generator/memes/mihoyo/__init__.py +33 -0
  163. meme_generator_next-0.2.0/meme_generator/memes/mourning/__init__.py +55 -0
  164. meme_generator_next-0.2.0/meme_generator/memes/murmur/__init__.py +36 -0
  165. meme_generator_next-0.2.0/meme_generator/memes/my_friend/__init__.py +96 -0
  166. meme_generator_next-0.2.0/meme_generator/memes/my_opinion/__init__.py +38 -0
  167. meme_generator_next-0.2.0/meme_generator/memes/my_wife/__init__.py +102 -0
  168. meme_generator_next-0.2.0/meme_generator/memes/nahida_bite/__init__.py +44 -0
  169. meme_generator_next-0.2.0/meme_generator/memes/name_generator/__init__.py +48 -0
  170. meme_generator_next-0.2.0/meme_generator/memes/need/__init__.py +30 -0
  171. meme_generator_next-0.2.0/meme_generator/memes/nekoha_holdsign/__init__.py +42 -0
  172. meme_generator_next-0.2.0/meme_generator/memes/nijika_holdsign/__init__.py +40 -0
  173. meme_generator_next-0.2.0/meme_generator/memes/no_response/__init__.py +26 -0
  174. meme_generator_next-0.2.0/meme_generator/memes/nokia/__init__.py +44 -0
  175. meme_generator_next-0.2.0/meme_generator/memes/not_call_me/__init__.py +37 -0
  176. meme_generator_next-0.2.0/meme_generator/memes/note_for_leave/__init__.py +107 -0
  177. meme_generator_next-0.2.0/meme_generator/memes/oshi_no_ko/__init__.py +75 -0
  178. meme_generator_next-0.2.0/meme_generator/memes/osu/__init__.py +42 -0
  179. meme_generator_next-0.2.0/meme_generator/memes/out/__init__.py +38 -0
  180. meme_generator_next-0.2.0/meme_generator/memes/overtime/__init__.py +26 -0
  181. meme_generator_next-0.2.0/meme_generator/memes/p5letter/__init__.py +158 -0
  182. meme_generator_next-0.2.0/meme_generator/memes/paint/__init__.py +26 -0
  183. meme_generator_next-0.2.0/meme_generator/memes/painter/__init__.py +30 -0
  184. meme_generator_next-0.2.0/meme_generator/memes/panda_dragon_figure/__init__.py +88 -0
  185. meme_generator_next-0.2.0/meme_generator/memes/pass_the_buck/__init__.py +44 -0
  186. meme_generator_next-0.2.0/meme_generator/memes/pat/__init__.py +37 -0
  187. meme_generator_next-0.2.0/meme_generator/memes/pepe_raise/__init__.py +51 -0
  188. meme_generator_next-0.2.0/meme_generator/memes/perfect/__init__.py +26 -0
  189. meme_generator_next-0.2.0/meme_generator/memes/petpet/__init__.py +67 -0
  190. meme_generator_next-0.2.0/meme_generator/memes/pinch/__init__.py +33 -0
  191. meme_generator_next-0.2.0/meme_generator/memes/pixelate/__init__.py +59 -0
  192. meme_generator_next-0.2.0/meme_generator/memes/pjsk/__init__.py +154 -0
  193. meme_generator_next-0.2.0/meme_generator/memes/plana_eat/__init__.py +32 -0
  194. meme_generator_next-0.2.0/meme_generator/memes/play/__init__.py +54 -0
  195. meme_generator_next-0.2.0/meme_generator/memes/play_baseball/__init__.py +53 -0
  196. meme_generator_next-0.2.0/meme_generator/memes/play_basketball/__init__.py +84 -0
  197. meme_generator_next-0.2.0/meme_generator/memes/play_game/__init__.py +54 -0
  198. meme_generator_next-0.2.0/meme_generator/memes/play_together/__init__.py +61 -0
  199. meme_generator_next-0.2.0/meme_generator/memes/police/__init__.py +68 -0
  200. meme_generator_next-0.2.0/meme_generator/memes/pornhub/__init__.py +41 -0
  201. meme_generator_next-0.2.0/meme_generator/memes/potato/__init__.py +26 -0
  202. meme_generator_next-0.2.0/meme_generator/memes/pound/__init__.py +38 -0
  203. meme_generator_next-0.2.0/meme_generator/memes/printing/__init__.py +39 -0
  204. meme_generator_next-0.2.0/meme_generator/memes/prpr/__init__.py +36 -0
  205. meme_generator_next-0.2.0/meme_generator/memes/psyduck/__init__.py +78 -0
  206. meme_generator_next-0.2.0/meme_generator/memes/punch/__init__.py +39 -0
  207. meme_generator_next-0.2.0/meme_generator/memes/pyramid/__init__.py +99 -0
  208. meme_generator_next-0.2.0/meme_generator/memes/raise_image/__init__.py +38 -0
  209. meme_generator_next-0.2.0/meme_generator/memes/raise_sign/__init__.py +43 -0
  210. meme_generator_next-0.2.0/meme_generator/memes/read_book/__init__.py +70 -0
  211. meme_generator_next-0.2.0/meme_generator/memes/remote_control/__init__.py +52 -0
  212. meme_generator_next-0.2.0/meme_generator/memes/repeat/__init__.py +70 -0
  213. meme_generator_next-0.2.0/meme_generator/memes/rip/__init__.py +38 -0
  214. meme_generator_next-0.2.0/meme_generator/memes/rip_angrily/__init__.py +27 -0
  215. meme_generator_next-0.2.0/meme_generator/memes/rip_clothes/__init__.py +38 -0
  216. meme_generator_next-0.2.0/meme_generator/memes/rise_dead/__init__.py +44 -0
  217. meme_generator_next-0.2.0/meme_generator/memes/roll/__init__.py +38 -0
  218. meme_generator_next-0.2.0/meme_generator/memes/rotate_3d/__init__.py +79 -0
  219. meme_generator_next-0.2.0/meme_generator/memes/rub/__init__.py +47 -0
  220. meme_generator_next-0.2.0/meme_generator/memes/run/__init__.py +40 -0
  221. meme_generator_next-0.2.0/meme_generator/memes/run_away/__init__.py +47 -0
  222. meme_generator_next-0.2.0/meme_generator/memes/safe_sense/__init__.py +48 -0
  223. meme_generator_next-0.2.0/meme_generator/memes/saimin_app/__init__.py +45 -0
  224. meme_generator_next-0.2.0/meme_generator/memes/scratch_head/__init__.py +40 -0
  225. meme_generator_next-0.2.0/meme_generator/memes/scratchcard/__init__.py +41 -0
  226. meme_generator_next-0.2.0/meme_generator/memes/scroll/__init__.py +61 -0
  227. meme_generator_next-0.2.0/meme_generator/memes/seal/__init__.py +38 -0
  228. meme_generator_next-0.2.0/meme_generator/memes/sekaiichi_kawaii/__init__.py +45 -0
  229. meme_generator_next-0.2.0/meme_generator/memes/shake_head/__init__.py +56 -0
  230. meme_generator_next-0.2.0/meme_generator/memes/shiroko_pero/__init__.py +41 -0
  231. meme_generator_next-0.2.0/meme_generator/memes/shock/__init__.py +31 -0
  232. meme_generator_next-0.2.0/meme_generator/memes/shutup/__init__.py +37 -0
  233. meme_generator_next-0.2.0/meme_generator/memes/sit_still/__init__.py +41 -0
  234. meme_generator_next-0.2.0/meme_generator/memes/slap/__init__.py +36 -0
  235. meme_generator_next-0.2.0/meme_generator/memes/slogan/__init__.py +49 -0
  236. meme_generator_next-0.2.0/meme_generator/memes/smash/__init__.py +36 -0
  237. meme_generator_next-0.2.0/meme_generator/memes/sold_out/__init__.py +40 -0
  238. meme_generator_next-0.2.0/meme_generator/memes/speechless/__init__.py +52 -0
  239. meme_generator_next-0.2.0/meme_generator/memes/sphere_rotate/__init__.py +166 -0
  240. meme_generator_next-0.2.0/meme_generator/memes/spider/__init__.py +50 -0
  241. meme_generator_next-0.2.0/meme_generator/memes/stare_at_you/__init__.py +49 -0
  242. meme_generator_next-0.2.0/meme_generator/memes/steam_message/__init__.py +101 -0
  243. meme_generator_next-0.2.0/meme_generator/memes/step_on/__init__.py +39 -0
  244. meme_generator_next-0.2.0/meme_generator/memes/stew/__init__.py +46 -0
  245. meme_generator_next-0.2.0/meme_generator/memes/stickman_dancing/__init__.py +62 -0
  246. meme_generator_next-0.2.0/meme_generator/memes/subject3/__init__.py +53 -0
  247. meme_generator_next-0.2.0/meme_generator/memes/suck/__init__.py +38 -0
  248. meme_generator_next-0.2.0/meme_generator/memes/support/__init__.py +26 -0
  249. meme_generator_next-0.2.0/meme_generator/memes/swirl_turn/__init__.py +41 -0
  250. meme_generator_next-0.2.0/meme_generator/memes/symmetric/__init__.py +134 -0
  251. meme_generator_next-0.2.0/meme_generator/memes/tankuku_raisesign/__init__.py +51 -0
  252. meme_generator_next-0.2.0/meme_generator/memes/taunt/__init__.py +30 -0
  253. meme_generator_next-0.2.0/meme_generator/memes/teach/__init__.py +50 -0
  254. meme_generator_next-0.2.0/meme_generator/memes/tease/__init__.py +61 -0
  255. meme_generator_next-0.2.0/meme_generator/memes/telescope/__init__.py +52 -0
  256. meme_generator_next-0.2.0/meme_generator/memes/thermometer_gun/__init__.py +61 -0
  257. meme_generator_next-0.2.0/meme_generator/memes/think_what/__init__.py +30 -0
  258. meme_generator_next-0.2.0/meme_generator/memes/this_chicken/__init__.py +49 -0
  259. meme_generator_next-0.2.0/meme_generator/memes/throw/__init__.py +35 -0
  260. meme_generator_next-0.2.0/meme_generator/memes/throw_gif/__init__.py +42 -0
  261. meme_generator_next-0.2.0/meme_generator/memes/thump/__init__.py +35 -0
  262. meme_generator_next-0.2.0/meme_generator/memes/thump_wildly/__init__.py +34 -0
  263. meme_generator_next-0.2.0/meme_generator/memes/tightly/__init__.py +38 -0
  264. meme_generator_next-0.2.0/meme_generator/memes/time_to_go/__init__.py +43 -0
  265. meme_generator_next-0.2.0/meme_generator/memes/together/__init__.py +49 -0
  266. meme_generator_next-0.2.0/meme_generator/memes/tom_tease/__init__.py +39 -0
  267. meme_generator_next-0.2.0/meme_generator/memes/tomb_yeah/__init__.py +33 -0
  268. meme_generator_next-0.2.0/meme_generator/memes/top_notch/__init__.py +37 -0
  269. meme_generator_next-0.2.0/meme_generator/memes/trance/__init__.py +31 -0
  270. meme_generator_next-0.2.0/meme_generator/memes/trolley/__init__.py +59 -0
  271. meme_generator_next-0.2.0/meme_generator/memes/turn/__init__.py +31 -0
  272. meme_generator_next-0.2.0/meme_generator/memes/twist/__init__.py +38 -0
  273. meme_generator_next-0.2.0/meme_generator/memes/universal/__init__.py +44 -0
  274. meme_generator_next-0.2.0/meme_generator/memes/upside_down/__init__.py +48 -0
  275. meme_generator_next-0.2.0/meme_generator/memes/vibrate/__init__.py +44 -0
  276. meme_generator_next-0.2.0/meme_generator/memes/wakeup/__init__.py +41 -0
  277. meme_generator_next-0.2.0/meme_generator/memes/wallpaper/__init__.py +35 -0
  278. meme_generator_next-0.2.0/meme_generator/memes/walnut_pad/__init__.py +32 -0
  279. meme_generator_next-0.2.0/meme_generator/memes/walnut_zoom/__init__.py +46 -0
  280. meme_generator_next-0.2.0/meme_generator/memes/washer/__init__.py +34 -0
  281. meme_generator_next-0.2.0/meme_generator/memes/wave/__init__.py +54 -0
  282. meme_generator_next-0.2.0/meme_generator/memes/wechat_pay/__init__.py +92 -0
  283. meme_generator_next-0.2.0/meme_generator/memes/what_I_want_to_do/__init__.py +30 -0
  284. meme_generator_next-0.2.0/meme_generator/memes/what_he_wants/__init__.py +52 -0
  285. meme_generator_next-0.2.0/meme_generator/memes/why_at_me/__init__.py +28 -0
  286. meme_generator_next-0.2.0/meme_generator/memes/why_have_hands/__init__.py +79 -0
  287. meme_generator_next-0.2.0/meme_generator/memes/windmill_turn/__init__.py +35 -0
  288. meme_generator_next-0.2.0/meme_generator/memes/wish_fail/__init__.py +37 -0
  289. meme_generator_next-0.2.0/meme_generator/memes/wooden_fish/__init__.py +29 -0
  290. meme_generator_next-0.2.0/meme_generator/memes/worship/__init__.py +33 -0
  291. meme_generator_next-0.2.0/meme_generator/memes/wujing/__init__.py +65 -0
  292. meme_generator_next-0.2.0/meme_generator/memes/yeshu/__init__.py +227 -0
  293. meme_generator_next-0.2.0/meme_generator/memes/you_dont_get/__init__.py +28 -0
  294. meme_generator_next-0.2.0/meme_generator/memes/you_should_call/__init__.py +54 -0
  295. meme_generator_next-0.2.0/meme_generator/memes/your_new_years_eve/__init__.py +34 -0
  296. meme_generator_next-0.2.0/meme_generator/memes/youtube/__init__.py +65 -0
  297. meme_generator_next-0.2.0/meme_generator/redis_cache.py +37 -0
  298. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f602.png +0 -0
  299. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f605.png +0 -0
  300. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f60f.png +0 -0
  301. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f62d.png +0 -0
  302. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f642.png +0 -0
  303. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f913.png +0 -0
  304. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f917.png +0 -0
  305. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f924.png +0 -0
  306. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f970.png +0 -0
  307. meme_generator_next-0.2.0/meme_generator/resources/images/emojis/emoji_u1f975.png +0 -0
  308. meme_generator_next-0.2.0/meme_generator/resources/images/icons/hot.png +0 -0
  309. meme_generator_next-0.2.0/meme_generator/resources/images/icons/image.png +0 -0
  310. meme_generator_next-0.2.0/meme_generator/resources/images/icons/image_disabled.png +0 -0
  311. meme_generator_next-0.2.0/meme_generator/resources/images/icons/new.png +0 -0
  312. meme_generator_next-0.2.0/meme_generator/resources/images/icons/text.png +0 -0
  313. meme_generator_next-0.2.0/meme_generator/resources/images/icons/text_disabled.png +0 -0
  314. meme_generator_next-0.2.0/meme_generator/storage.py +110 -0
  315. meme_generator_next-0.2.0/meme_generator/tags.py +146 -0
  316. meme_generator_next-0.2.0/meme_generator/utils.py +517 -0
  317. meme_generator_next-0.2.0/meme_generator/version.py +1 -0
  318. meme_generator_next-0.2.0/meme_generator_cli.py +36 -0
  319. meme_generator_next-0.2.0/pyproject.toml +64 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chiskat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: meme-generator-next
3
+ Version: 0.2.0
4
+ Summary: The next-generation Python version of meme-generator
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: chiskat
8
+ Author-email: 1@paperplane.cc
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: Pillow (>=12.3.0,<13.0.0)
18
+ Requires-Dist: arclet-alconna (>=1.8.23,<2.0.0,!=1.8.27)
19
+ Requires-Dist: arclet-alconna-tools (>=0.7.9,<0.8.0)
20
+ Requires-Dist: boto3 (>=1.35.0,<2.0.0)
21
+ Requires-Dist: dateparser (>=1.2.0,<2.0.0)
22
+ Requires-Dist: fastapi (>=0.93.0,<1.0.0)
23
+ Requires-Dist: filetype (>=1.2.0,<2.0.0)
24
+ Requires-Dist: httpx (>=0.20.0,<1.0.0)
25
+ Requires-Dist: loguru (>=0.6.0,<1.0.0)
26
+ Requires-Dist: pil-utils-fix-deps (>=0.2.0,<0.3.0)
27
+ Requires-Dist: pycairo (>=1.26.0)
28
+ Requires-Dist: python-multipart (>=0.0.9,<0.1.0,!=0.0.13,!=0.0.14)
29
+ Requires-Dist: qrcode (>=8.0,<9.0)
30
+ Requires-Dist: redis (>=5.0.0,<6.0.0)
31
+ Requires-Dist: rich (>=13.0.0,<14.0.0)
32
+ Requires-Dist: skia-python (>=138.0)
33
+ Requires-Dist: toml (>=0.10.2,<0.11.0)
34
+ Requires-Dist: typing-extensions (>=4.4.0,<5.0.0)
35
+ Requires-Dist: uvicorn (>=0.20.0,<1.0.0)
36
+ Project-URL: Homepage, https://github.com/chiskat/meme-generator-next
37
+ Project-URL: Repository, https://github.com/chiskat/meme-generator-next
38
+ Description-Content-Type: text/markdown
39
+
40
+ # meme-generator-next
41
+
42
+ 本项目 fork 自 [MemeCrafters/meme-generator](https://github.com/MemeCrafters/meme-generator)。
43
+
44
+ 特性升级:
45
+
46
+ - 处理了 `Pillow` 和 `pil-utils` 的依赖版本,以避免和 Astrbot 新版本不兼容;
47
+ - 依赖项包含 `pycairo`,因此在加载 [meme_emoji](https://github.com/anyliew/meme_emoji) 时不再提示缺少依赖;
48
+ - 支持自定义 `config.toml` 文件路径,详见 [配置文件路径](#配置文件路径);
49
+ - 支持自动上传 S3 并返回 URL;
50
+ - 支持通过 Redis 连接,缓存预览图。
51
+
52
+ # 特性升级介绍
53
+
54
+ ## 配置文件路径
55
+
56
+ 作为包导入或 Docker 部署时,可通过 `MEME_GENERATOR_CONFIG_FILE` 指定配置文件路径,文件名不必为 `config.toml`:
57
+
58
+ ```yaml
59
+ services:
60
+ meme-generator:
61
+ volumes:
62
+ - ./config:/config
63
+ environment:
64
+ MEME_GENERATOR_CONFIG_FILE: /config/config.toml
65
+ ```
66
+
67
+ 命令行启动时,可使用 `--config-file` 参数指定配置文件路径:
68
+
69
+ ```bash
70
+ # CLI 用法
71
+ meme --config-file /path/to/config.toml run
72
+
73
+ # Web Server
74
+ python -m meme_generator.app --config-file=/path/to/config.toml
75
+ ```
76
+
77
+ 兼容说明:旧版本的 `MEME_GENERATOR_CONFIG_DIR` 环境变量(目录形式)仍然有效,但已不推荐使用;`--config-dir` 参数已被 `--config-file` 取代。
78
+
79
+ ## S3 存储
80
+
81
+ 在 `config.toml` 中启用 S3 后,HTTP Server 会为每个表情新增一个 `/memes/{key}/url/` 端点。该端点的请求参数与原有的 `/memes/{key}/` 端点完全相同,生成成功后会自动上传到 S3,并返回 JSON:
82
+
83
+ ```json
84
+ {
85
+ "url": "https://cdn.example.com/meme-generator/artifact/petpet/xxx.gif",
86
+ "meme_key": "petpet",
87
+ "object_key": "meme-generator/artifact/petpet/xxx.gif"
88
+ }
89
+ ```
90
+
91
+ 生成结果的对象名格式为 `{result_prefix}/{meme_key}/{random_key}.{extension}`,预览图为 `{preview_prefix}/{meme_key}/{random_key}.{extension}`。其中 `random_key` 为 32 位 UUID 随机十六进制字符串,不包含日期。
92
+
93
+ 生成结果和预览图分别使用 `result_prefix` 与 `preview_prefix`,没有通用对象前缀配置。
94
+
95
+ 配置示例:
96
+
97
+ ```toml
98
+ [storage.s3]
99
+ enabled = true
100
+ # 自定义 S3 兼容服务地址;AWS 官方 S3 可以留空
101
+ endpoint_url = "https://s3.example.com"
102
+ region = "ap-northeast-1"
103
+ bucket = "meme-generator"
104
+ access_key_id = "your-access-key-id"
105
+ secret_access_key = "your-secret-access-key"
106
+ # 生成结果前缀
107
+ result_prefix = "meme-generator/artifact"
108
+ # 预览图前缀
109
+ preview_prefix = "meme-generator/preview"
110
+ # 推荐配置为 bucket 的公开访问地址;留空时会根据 endpoint/region 生成 S3 URL
111
+ public_base_url = "https://cdn.example.com"
112
+ # 大多数 S3 兼容服务(MinIO、Cloudflare R2 等)需要 path_style = true
113
+ path_style = true
114
+ ```
115
+
116
+ 返回的 `url` 需要能被访问;如果使用私有 bucket,请将 `public_base_url` 指向具有读取权限的 CDN/反向代理,或自行生成预签名 URL。
117
+
118
+ Docker 部署时,也可以使用环境变量来配置:
119
+
120
+ ```yaml
121
+ services:
122
+ meme-generator:
123
+ environment:
124
+ S3_ENABLED: 'true'
125
+ S3_ENDPOINT_URL: 'https://s3.example.com'
126
+ S3_REGION: 'ap-northeast-1'
127
+ S3_BUCKET: 'meme-generator'
128
+ S3_ACCESS_KEY_ID: 'your-access-key-id'
129
+ S3_SECRET_ACCESS_KEY: 'your-secret-access-key'
130
+ S3_SESSION_TOKEN: ''
131
+ S3_RESULT_PREFIX: 'meme-generator/artifact'
132
+ S3_PREVIEW_PREFIX: 'meme-generator/preview'
133
+ S3_PUBLIC_BASE_URL: 'https://cdn.example.com'
134
+ S3_PATH_STYLE: 'true'
135
+ ```
136
+
137
+ ## Redis 预览缓存
138
+
139
+ 启用 S3 和 Redis 后,每个表情都会新增 `GET /memes/{key}/preview/url/` 端点。第一次访问时会生成预览图并上传到 S3,然后将返回的公开 URL 写入 Redis;后续访问会直接返回缓存中的 URL:
140
+
141
+ ```json
142
+ {
143
+ "url": "https://cdn.example.com/meme-generator/preview/petpet/xxx.gif",
144
+ "meme_key": "petpet"
145
+ }
146
+ ```
147
+
148
+ Redis 配置示例:
149
+
150
+ ```toml
151
+ [storage.redis]
152
+ enabled = true
153
+ url = "redis://localhost:6379/0"
154
+ key_prefix = "meme-generator:preview"
155
+ ```
156
+
157
+ `key_prefix` 用于区分不同部署的缓存键。缓存写入时不设置过期时间;如果 Redis 配置了 `maxmemory-policy`,请避免选择会主动删除键的淘汰策略,或使用专门的 Redis 实例。
158
+
159
+ Docker 部署时,也可以使用环境变量来配置:
160
+
161
+ ```yaml
162
+ services:
163
+ meme-generator:
164
+ environment:
165
+ REDIS_ENABLED: 'true'
166
+ REDIS_URL: 'redis://localhost:6379/0'
167
+ REDIS_KEY_PREFIX: 'meme-generator:preview'
168
+ ```
169
+
@@ -0,0 +1,129 @@
1
+ # meme-generator-next
2
+
3
+ 本项目 fork 自 [MemeCrafters/meme-generator](https://github.com/MemeCrafters/meme-generator)。
4
+
5
+ 特性升级:
6
+
7
+ - 处理了 `Pillow` 和 `pil-utils` 的依赖版本,以避免和 Astrbot 新版本不兼容;
8
+ - 依赖项包含 `pycairo`,因此在加载 [meme_emoji](https://github.com/anyliew/meme_emoji) 时不再提示缺少依赖;
9
+ - 支持自定义 `config.toml` 文件路径,详见 [配置文件路径](#配置文件路径);
10
+ - 支持自动上传 S3 并返回 URL;
11
+ - 支持通过 Redis 连接,缓存预览图。
12
+
13
+ # 特性升级介绍
14
+
15
+ ## 配置文件路径
16
+
17
+ 作为包导入或 Docker 部署时,可通过 `MEME_GENERATOR_CONFIG_FILE` 指定配置文件路径,文件名不必为 `config.toml`:
18
+
19
+ ```yaml
20
+ services:
21
+ meme-generator:
22
+ volumes:
23
+ - ./config:/config
24
+ environment:
25
+ MEME_GENERATOR_CONFIG_FILE: /config/config.toml
26
+ ```
27
+
28
+ 命令行启动时,可使用 `--config-file` 参数指定配置文件路径:
29
+
30
+ ```bash
31
+ # CLI 用法
32
+ meme --config-file /path/to/config.toml run
33
+
34
+ # Web Server
35
+ python -m meme_generator.app --config-file=/path/to/config.toml
36
+ ```
37
+
38
+ 兼容说明:旧版本的 `MEME_GENERATOR_CONFIG_DIR` 环境变量(目录形式)仍然有效,但已不推荐使用;`--config-dir` 参数已被 `--config-file` 取代。
39
+
40
+ ## S3 存储
41
+
42
+ 在 `config.toml` 中启用 S3 后,HTTP Server 会为每个表情新增一个 `/memes/{key}/url/` 端点。该端点的请求参数与原有的 `/memes/{key}/` 端点完全相同,生成成功后会自动上传到 S3,并返回 JSON:
43
+
44
+ ```json
45
+ {
46
+ "url": "https://cdn.example.com/meme-generator/artifact/petpet/xxx.gif",
47
+ "meme_key": "petpet",
48
+ "object_key": "meme-generator/artifact/petpet/xxx.gif"
49
+ }
50
+ ```
51
+
52
+ 生成结果的对象名格式为 `{result_prefix}/{meme_key}/{random_key}.{extension}`,预览图为 `{preview_prefix}/{meme_key}/{random_key}.{extension}`。其中 `random_key` 为 32 位 UUID 随机十六进制字符串,不包含日期。
53
+
54
+ 生成结果和预览图分别使用 `result_prefix` 与 `preview_prefix`,没有通用对象前缀配置。
55
+
56
+ 配置示例:
57
+
58
+ ```toml
59
+ [storage.s3]
60
+ enabled = true
61
+ # 自定义 S3 兼容服务地址;AWS 官方 S3 可以留空
62
+ endpoint_url = "https://s3.example.com"
63
+ region = "ap-northeast-1"
64
+ bucket = "meme-generator"
65
+ access_key_id = "your-access-key-id"
66
+ secret_access_key = "your-secret-access-key"
67
+ # 生成结果前缀
68
+ result_prefix = "meme-generator/artifact"
69
+ # 预览图前缀
70
+ preview_prefix = "meme-generator/preview"
71
+ # 推荐配置为 bucket 的公开访问地址;留空时会根据 endpoint/region 生成 S3 URL
72
+ public_base_url = "https://cdn.example.com"
73
+ # 大多数 S3 兼容服务(MinIO、Cloudflare R2 等)需要 path_style = true
74
+ path_style = true
75
+ ```
76
+
77
+ 返回的 `url` 需要能被访问;如果使用私有 bucket,请将 `public_base_url` 指向具有读取权限的 CDN/反向代理,或自行生成预签名 URL。
78
+
79
+ Docker 部署时,也可以使用环境变量来配置:
80
+
81
+ ```yaml
82
+ services:
83
+ meme-generator:
84
+ environment:
85
+ S3_ENABLED: 'true'
86
+ S3_ENDPOINT_URL: 'https://s3.example.com'
87
+ S3_REGION: 'ap-northeast-1'
88
+ S3_BUCKET: 'meme-generator'
89
+ S3_ACCESS_KEY_ID: 'your-access-key-id'
90
+ S3_SECRET_ACCESS_KEY: 'your-secret-access-key'
91
+ S3_SESSION_TOKEN: ''
92
+ S3_RESULT_PREFIX: 'meme-generator/artifact'
93
+ S3_PREVIEW_PREFIX: 'meme-generator/preview'
94
+ S3_PUBLIC_BASE_URL: 'https://cdn.example.com'
95
+ S3_PATH_STYLE: 'true'
96
+ ```
97
+
98
+ ## Redis 预览缓存
99
+
100
+ 启用 S3 和 Redis 后,每个表情都会新增 `GET /memes/{key}/preview/url/` 端点。第一次访问时会生成预览图并上传到 S3,然后将返回的公开 URL 写入 Redis;后续访问会直接返回缓存中的 URL:
101
+
102
+ ```json
103
+ {
104
+ "url": "https://cdn.example.com/meme-generator/preview/petpet/xxx.gif",
105
+ "meme_key": "petpet"
106
+ }
107
+ ```
108
+
109
+ Redis 配置示例:
110
+
111
+ ```toml
112
+ [storage.redis]
113
+ enabled = true
114
+ url = "redis://localhost:6379/0"
115
+ key_prefix = "meme-generator:preview"
116
+ ```
117
+
118
+ `key_prefix` 用于区分不同部署的缓存键。缓存写入时不设置过期时间;如果 Redis 配置了 `maxmemory-policy`,请避免选择会主动删除键的淘汰策略,或使用专门的 Redis 实例。
119
+
120
+ Docker 部署时,也可以使用环境变量来配置:
121
+
122
+ ```yaml
123
+ services:
124
+ meme-generator:
125
+ environment:
126
+ REDIS_ENABLED: 'true'
127
+ REDIS_URL: 'redis://localhost:6379/0'
128
+ REDIS_KEY_PREFIX: 'meme-generator:preview'
129
+ ```
@@ -0,0 +1,59 @@
1
+ import argparse
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ CONFIG_FILE_ENV = "MEME_GENERATOR_CONFIG_FILE"
7
+
8
+
9
+ def _is_module_invocation(module_name: str) -> bool:
10
+ orig_argv = getattr(sys, "orig_argv", [])
11
+ try:
12
+ option_index = orig_argv.index("-m")
13
+ except ValueError:
14
+ return False
15
+ return (
16
+ option_index + 1 < len(orig_argv) and orig_argv[option_index + 1] == module_name
17
+ )
18
+
19
+
20
+ def _apply_config_file_option() -> None:
21
+ if not _is_module_invocation("meme_generator.app"):
22
+ return
23
+
24
+ parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
25
+ parser.add_argument("--config-file", dest="config_file")
26
+ args, remaining = parser.parse_known_args(sys.argv[1:])
27
+
28
+ if args.config_file is None:
29
+ return
30
+ if not args.config_file:
31
+ parser.error("--config-file must not be empty")
32
+
33
+ os.environ[CONFIG_FILE_ENV] = str(Path(args.config_file).expanduser())
34
+ sys.argv[:] = [sys.argv[0], *remaining]
35
+
36
+
37
+ _apply_config_file_option()
38
+
39
+ from meme_generator.config import meme_config as config
40
+ from meme_generator.manager import add_meme as add_meme
41
+ from meme_generator.manager import get_meme as get_meme
42
+ from meme_generator.manager import get_meme_keys as get_meme_keys
43
+ from meme_generator.manager import get_memes as get_memes
44
+ from meme_generator.manager import load_meme as load_meme
45
+ from meme_generator.manager import load_memes as load_memes
46
+ from meme_generator.meme import CommandShortcut as CommandShortcut
47
+ from meme_generator.meme import Meme as Meme
48
+ from meme_generator.meme import MemeArgsModel as MemeArgsModel
49
+ from meme_generator.meme import MemeArgsType as MemeArgsType
50
+ from meme_generator.meme import MemeParamsType as MemeParamsType
51
+ from meme_generator.meme import ParserArg as ParserArg
52
+ from meme_generator.meme import ParserOption as ParserOption
53
+ from meme_generator.version import __version__ as __version__
54
+
55
+ if config.meme.load_builtin_memes:
56
+ for path in (Path(__file__).parent / "memes").iterdir():
57
+ load_meme(f"meme_generator.memes.{path.name}")
58
+ for meme_dir in config.meme.meme_dirs:
59
+ load_memes(meme_dir)
@@ -0,0 +1,322 @@
1
+ import json
2
+ from datetime import datetime
3
+ from typing import Any, Literal, Optional
4
+
5
+ import filetype
6
+ from fastapi import Depends, FastAPI, Form, HTTPException, Response, UploadFile
7
+ from pydantic import BaseModel, ValidationError
8
+ from redis.exceptions import RedisError
9
+
10
+ from meme_generator.compat import model_dump, model_json_schema, type_validate_python
11
+ from meme_generator.config import meme_config
12
+ from meme_generator.exception import (
13
+ ArgModelMismatch,
14
+ MemeGeneratorException,
15
+ NoSuchMeme,
16
+ RedisCacheError,
17
+ S3StorageError,
18
+ )
19
+ from meme_generator.log import LOGGING_CONFIG, setup_logger
20
+ from meme_generator.manager import get_meme, get_meme_keys, get_memes
21
+ from meme_generator.meme import CommandShortcut, Meme, MemeArgsModel, ParserOption
22
+ from meme_generator.redis_cache import (
23
+ cache_preview_url,
24
+ create_redis_client,
25
+ get_cached_preview_url,
26
+ )
27
+ from meme_generator.storage import upload_to_s3
28
+ from meme_generator.utils import MemeProperties, render_meme_list, run_sync
29
+ from meme_generator.version import __version__
30
+
31
+ app = FastAPI()
32
+
33
+
34
+ class MemeArgsResponse(BaseModel):
35
+ args_model: dict[str, Any]
36
+ args_examples: list[dict[str, Any]]
37
+ parser_options: list[ParserOption]
38
+
39
+
40
+ class MemeParamsResponse(BaseModel):
41
+ min_images: int
42
+ max_images: int
43
+ min_texts: int
44
+ max_texts: int
45
+ default_texts: list[str]
46
+ args_type: Optional[MemeArgsResponse] = None
47
+
48
+
49
+ class MemeUrlResponse(BaseModel):
50
+ url: str
51
+ meme_key: str
52
+ object_key: str
53
+
54
+
55
+ class MemePreviewUrlResponse(BaseModel):
56
+ url: str
57
+ meme_key: str
58
+
59
+
60
+ class MemeInfoResponse(BaseModel):
61
+ key: str
62
+ params_type: MemeParamsResponse
63
+ keywords: list[str]
64
+ shortcuts: list[CommandShortcut]
65
+ tags: set[str]
66
+ date_created: datetime
67
+ date_modified: datetime
68
+
69
+
70
+ async def generate_meme(
71
+ meme: Meme, images: list[bytes], texts: list[str], args: Any
72
+ ) -> tuple[bytes, str]:
73
+ try:
74
+ result = await run_sync(meme)(images=images, texts=texts, args=model_dump(args))
75
+ except MemeGeneratorException as e:
76
+ raise HTTPException(status_code=e.status_code, detail=e.message)
77
+
78
+ content = result.getvalue()
79
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
80
+ return content, media_type
81
+
82
+
83
+ def register_router(meme: Meme):
84
+ if args_type := meme.params_type.args_type:
85
+ args_model = args_type.args_model
86
+ else:
87
+ args_model = MemeArgsModel
88
+
89
+ def args_checker(
90
+ args: Optional[str] = Form(default=json.dumps(model_dump(args_model()))),
91
+ ):
92
+ if not args:
93
+ return MemeArgsModel()
94
+ try:
95
+ model = type_validate_python(args_model, json.loads(args))
96
+ except ValidationError as e:
97
+ e = ArgModelMismatch(str(e))
98
+ raise HTTPException(status_code=552, detail=e.message)
99
+ return model
100
+
101
+ @app.post(f"/memes/{meme.key}/")
102
+ async def _(
103
+ images: list[UploadFile] = [],
104
+ texts: list[str] = meme.params_type.default_texts,
105
+ args: args_model = Depends(args_checker), # type: ignore
106
+ ):
107
+ imgs: list[bytes] = []
108
+ for image in images:
109
+ imgs.append(await image.read())
110
+
111
+ texts = [text for text in texts if text]
112
+
113
+ assert isinstance(args, args_model)
114
+
115
+ content, media_type = await generate_meme(meme, imgs, texts, args)
116
+
117
+ return Response(content=content, media_type=media_type)
118
+
119
+ @app.post(f"/memes/{meme.key}/url/")
120
+ async def _(
121
+ images: list[UploadFile] = [],
122
+ texts: list[str] = meme.params_type.default_texts,
123
+ args: args_model = Depends(args_checker), # type: ignore
124
+ ):
125
+ imgs: list[bytes] = []
126
+ for image in images:
127
+ imgs.append(await image.read())
128
+
129
+ texts = [text for text in texts if text]
130
+
131
+ assert isinstance(args, args_model)
132
+
133
+ content, media_type = await generate_meme(meme, imgs, texts, args)
134
+
135
+ try:
136
+ upload_result = await run_sync(upload_to_s3)(
137
+ content=content,
138
+ media_type=media_type,
139
+ meme_key=meme.key,
140
+ config=meme_config.storage.s3,
141
+ upload_kind="result",
142
+ )
143
+ except S3StorageError as e:
144
+ raise HTTPException(status_code=e.status_code, detail=e.message)
145
+
146
+ return MemeUrlResponse(
147
+ url=upload_result.url,
148
+ meme_key=meme.key,
149
+ object_key=upload_result.object_key,
150
+ )
151
+
152
+ @app.get(f"/memes/{meme.key}/preview/url/")
153
+ async def _():
154
+ redis_config = meme_config.storage.redis
155
+ if not redis_config.enabled:
156
+ error = RedisCacheError(
157
+ "Redis 缓存未启用,请在 config.toml 中配置 storage.redis"
158
+ )
159
+ raise HTTPException(status_code=error.status_code, detail=error.message)
160
+
161
+ try:
162
+ redis_client = create_redis_client(redis_config)
163
+ except RedisCacheError as e:
164
+ raise HTTPException(status_code=e.status_code, detail=e.message)
165
+
166
+ try:
167
+ cached_url = await get_cached_preview_url(
168
+ redis_client, redis_config, meme.key
169
+ )
170
+ if cached_url:
171
+ return MemePreviewUrlResponse(url=cached_url, meme_key=meme.key)
172
+
173
+ try:
174
+ result = await run_sync(meme.generate_preview)()
175
+ except MemeGeneratorException as e:
176
+ raise HTTPException(status_code=e.status_code, detail=e.message)
177
+
178
+ content = result.getvalue()
179
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
180
+
181
+ try:
182
+ upload_result = await run_sync(upload_to_s3)(
183
+ content=content,
184
+ media_type=media_type,
185
+ meme_key=meme.key,
186
+ config=meme_config.storage.s3,
187
+ upload_kind="preview",
188
+ )
189
+ except S3StorageError as e:
190
+ raise HTTPException(status_code=e.status_code, detail=e.message)
191
+
192
+ try:
193
+ await cache_preview_url(
194
+ redis_client, redis_config, meme.key, upload_result.url
195
+ )
196
+ except RedisError as e:
197
+ error = RedisCacheError(f"Redis 缓存写入失败:{e}")
198
+ raise HTTPException(status_code=error.status_code, detail=error.message)
199
+
200
+ return MemePreviewUrlResponse(url=upload_result.url, meme_key=meme.key)
201
+ except RedisError as e:
202
+ error = RedisCacheError(f"Redis 缓存读取失败:{e}")
203
+ raise HTTPException(status_code=error.status_code, detail=error.message)
204
+ finally:
205
+ await redis_client.aclose()
206
+
207
+
208
+ class MemeKeyWithProperties(BaseModel):
209
+ meme_key: str
210
+ disabled: bool = False
211
+ labels: list[Literal["new", "hot"]] = []
212
+
213
+
214
+ default_meme_list = [
215
+ MemeKeyWithProperties(meme_key=meme.key)
216
+ for meme in sorted(get_memes(), key=lambda meme: meme.key)
217
+ ]
218
+
219
+
220
+ class RenderMemeListRequest(BaseModel):
221
+ meme_list: list[MemeKeyWithProperties] = default_meme_list
222
+ text_template: str = "{keywords}"
223
+ add_category_icon: bool = True
224
+
225
+
226
+ def register_routers():
227
+ @app.post("/memes/render_list")
228
+ def _(params: RenderMemeListRequest = RenderMemeListRequest()):
229
+ try:
230
+ meme_list = [
231
+ (
232
+ get_meme(p.meme_key),
233
+ MemeProperties(disabled=p.disabled, labels=p.labels),
234
+ )
235
+ for p in params.meme_list
236
+ ]
237
+ except NoSuchMeme as e:
238
+ raise HTTPException(status_code=e.status_code, detail=e.message)
239
+
240
+ result = render_meme_list(
241
+ meme_list,
242
+ text_template=params.text_template,
243
+ add_category_icon=params.add_category_icon,
244
+ )
245
+ content = result.getvalue()
246
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
247
+ return Response(content=content, media_type=media_type)
248
+
249
+ @app.get("/meme/version")
250
+ def _():
251
+ return __version__
252
+
253
+ @app.get("/memes/keys")
254
+ def _():
255
+ return get_meme_keys()
256
+
257
+ @app.get("/memes/{key}/info")
258
+ def _(key: str):
259
+ try:
260
+ meme = get_meme(key)
261
+ except NoSuchMeme as e:
262
+ raise HTTPException(status_code=e.status_code, detail=e.message)
263
+
264
+ args_type_response = None
265
+ if args_type := meme.params_type.args_type:
266
+ args_model = args_type.args_model
267
+ args_type_response = MemeArgsResponse(
268
+ args_model=model_json_schema(args_model),
269
+ args_examples=[
270
+ model_dump(example) for example in args_type.args_examples
271
+ ],
272
+ parser_options=args_type.parser_options,
273
+ )
274
+
275
+ return MemeInfoResponse(
276
+ key=meme.key,
277
+ params_type=MemeParamsResponse(
278
+ min_images=meme.params_type.min_images,
279
+ max_images=meme.params_type.max_images,
280
+ min_texts=meme.params_type.min_texts,
281
+ max_texts=meme.params_type.max_texts,
282
+ default_texts=meme.params_type.default_texts,
283
+ args_type=args_type_response,
284
+ ),
285
+ keywords=meme.keywords,
286
+ shortcuts=meme.shortcuts,
287
+ tags=meme.tags,
288
+ date_created=meme.date_created,
289
+ date_modified=meme.date_modified,
290
+ )
291
+
292
+ @app.get("/memes/{key}/preview")
293
+ async def _(key: str):
294
+ try:
295
+ meme = get_meme(key)
296
+ result = await run_sync(meme.generate_preview)()
297
+ except MemeGeneratorException as e:
298
+ raise HTTPException(status_code=e.status_code, detail=e.message)
299
+
300
+ content = result.getvalue()
301
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
302
+ return Response(content=content, media_type=media_type)
303
+
304
+ for meme in sorted(get_memes(), key=lambda meme: meme.key):
305
+ register_router(meme)
306
+
307
+
308
+ def run_server():
309
+ import uvicorn
310
+
311
+ register_routers()
312
+ uvicorn.run(
313
+ app,
314
+ host=meme_config.server.host,
315
+ port=meme_config.server.port,
316
+ log_config=LOGGING_CONFIG,
317
+ )
318
+
319
+
320
+ if __name__ == "__main__":
321
+ setup_logger()
322
+ run_server()